详细分析Vue3中组合式API(附Demo)

时间:2025-04-07 15:01:25

目录

  • 前言
  • 1. 基本知识
  • 2. Demo
    • 2.1 选项式
    • 2.2 组合式
  • 3. 细节
    • 3.1 setup
    • 3.2 watch
    • 3.3 computed

前言

对于Vue2转Vue3的项目中,此文了解所必备的特性知识点

1. 基本知识

Vue 3 引入了组合式 API(Composition API),这是一个强大的新功能,与传统的选项式 API(Options API)相比,提供了更灵活、更易于复用的方式来组织和复用代码

  1. setup 函数
    组合式 API 的核心
    每个组件的 setup 函数在组件实例创建之前执行,用于定义组件的响应式状态和逻辑
    setup 函数接收两个参数:propscontext
  • props::组件的属性对象
  • context:包含 attrs, slots, 和 emit 的对象(详细分析Vue3中的emit用法(子传父)
  1. 响应式数据
  • reactive:创建一个响应式对象(详细分析Vue3中的reactive(附Demo)
  • ref:创建一个包含单一值的响应式引用(详细分析Vue3中的ref(附Demo)
  1. 计算属性

computed:创建一个计算属性,依赖的值变化时自动更新

  1. 侦听器
  • watch:侦听响应式数据的变化并执行副作用
  • watchEffect:立即执行传入的副作用函数,并在其依赖项发生变化时重新执行
  1. 生命周期钩子

在组合式 API 中,生命周期钩子通过 onXxx 函数来实现,例如 onMounted, onUpdated, onUnmounted

2. Demo

感受两者的差异

特性/方面 选项式 API 组合式 API
定义组件状态 使用 data 选项,返回一个包含状态的对象 使用 reactive 或 ref 函数
定义方法 使用 methods 选项定义方法 在 setup 函数中定义方法
计算属性 使用 computed 选项定义 使用 computed 函数
侦听器 使用 watch 选项定义 使用 watch 和 watchEffect 函数
生命周期钩子 使用 created, mounted 等选项 使用 onMounted, onUpdated 等函数
代码组织 逻辑分散在不同选项中,如 data, methods 等 逻辑集中在 setup 函数中,便于组织和复用
复用逻辑 通过混入 (mixins) 或高阶组件 (HOC) 复用逻辑 通过组合函数 (composition functions) 复用逻辑
类型支持 对 TypeScript 支持有限,类型推断不够强 对 TypeScript 支持更好,类型推断更强
模块化 逻辑复用和模块化较难,代码容易变得复杂 更易模块化,逻辑复用更灵活
性能 大多数场景下性能相当,但逻辑复用场景下性能较低 更高的性能和更低的内存占用,尤其在复杂逻辑中

2.1 选项式

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
  </div>
</template>

<script>
export default {
  // data 选项用于定义组件的响应式状态
  data() {
    return {
      count: 0
    };
  },
  // methods 选项用于定义组件的方法
  methods: {
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    }
  }
};
</script>

2.2 组合式

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
  </div>
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    // 创建一个响应式的 count 变量
    const count = ref(0);

    // 定义递增函数
    const increment = () => {
      count.value++;
    };

    // 定义递减函数
    const decrement = () => {
      count.value--;
    };

    // 返回供模板使用的数据和方法
    return {
      count,
      increment,
      decrement
    };
  }
};
</script>

3. 细节

3.1 setup

在组件实例创建之前执行,用于初始化组件的状态和定义组件的逻辑

setup(props, context) {
  const { attrs, slots, emit } = context;
}

setup 函数返回一个对象,该对象的属性和方法会被暴露给组件的模板

  • 响应式数据(由 ref 或 reactive 创建)
  • 计算属性(由 computed 创建)
  • 方法(普通函数)
setup() {
  const count = ref(0);
  const increment = () => {
    count.value++;
  };
  return { count, increment };
}

需要注意的点:

  • 在 setup 中,不能使用 this 访问组件实例,因为组件实例在 setup 执行时尚未创建
  • 组合式 API 中的生命周期钩子函数(如 onMounted, onUnmounted)需要在 setup 函数中调用,而不是在组件选项中定义
  • props 是响应式的,可以解构使用,但不建议解构,因为这会导致响应性丢失。使用时应直接使用 props 对象
  • context 的 attrs, slots, 和 emit 也是响应式的,可以用于更灵活的组件开发

示例Demo如下:

<template>
  <div v-bind="attrs" @click="handleClick">
    <p>{{ message }}</p>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <slot></slot>
  </div>
</template>

<script>
import { ref, reactive, computed, watch, onMounted } from 'vue';

export default {
  props: {
    initialCount: {
      type: Number,
      default: 0
    }
  },
  setup(props, { attrs, slots, emit }) {
    // 响应式状态
    const count = ref(props.initialCount);
    const state = reactive({ message: 'Hello Vue 3' });

    // 计算属性
    const doubleCount = computed(() => count.value * 2);

    // 侦听器
    watch(count, (newVal, oldVal) => {
      console.log(`Count changed from ${oldVal} to ${newVal}`);
    });

    // 方法
    const handleClick = () => {
      emit('custom-event', count.value);
    };

    // 生命周期钩子
    onMounted(() => {
      console.log('Component mounted');
    });

    return {
      count,
      state,
      doubleCount,
      handleClick,
      attrs,  // 可在模板中使用
      slots   // 可在模板中使用
    };
  }
};
</script>

3.2 watch

Vue 3 中,watch 用于监听响应式数据的变化并在变化时执行特定的副作用

watch 在组合式 API 中提供了更灵活的监听机制,相较于 Vue 2 的 watch 选项,Vue 3 的 watch 有一些重要的变化和增强

特性/方面 Vue 2 选项式 API Vue 3 组合式 API
定义位置 在组件选项的 watch 对象中 在 setup 函数中使用 watch 函数
监听多个源 不支持 支持
即时执行回调 不支持 支持,通过 { immediate: true } 选项
深度监听 通过 { deep: true } 选项 支持,通过 { deep: true } 选项
回调参数 新值和旧值 新值和旧值
可组合性 逻辑分散在不同的选项中 逻辑集中在 setup 函数中,更易组织和复用
  • 可以监听 ref、reactive 对象以及计算属性的变化
  • 监听多个响应式数据源,并在任一源变化时触发回调
  • 通过选项配置立即执行回调函数,而不仅是在数据变化时
  • 深度监听嵌套对象的变化
  • 回调函数接收两个参数:新值和旧值,可以更灵活地处理变化

基本的用法如下:

import { ref, watch } from 'vue';

export default {
  setup() {
    const count = ref(0);

    watch(count, (newValue, oldValue) => {
      console.log(`Count changed from ${oldValue} to ${newValue}`);
    });

    const increment = () => {
      count.value++;
    };

    return {
      count,
      increment
    };
  }
};
  • 监听多个源:
import { ref, watch } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const name = ref('Vue');

    watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
      console.log(`Count changed from ${oldCount} to ${newCount}`);
      console.log(`Name changed from ${oldName} to ${newName}`);
    });

    const increment = () => {
      count.value++;
    };

    const changeName = () => {
      name.value = name.value === 'Vue' ? '' : 'Vue';
    };

    return {
      count,
      name,
      increment,
      changeName
    };
  }
};
  • 深度监听嵌套对象
import { reactive, watch } from 'vue';

export default {
  setup() {
    const user = reactive({
      name: 'John Doe',
      address: {
        city: 'New York',
        zip: '10001'
      }
    });

    watch(user, (newValue, oldValue) => {
      console.log('User changed:', newValue);
    }, { deep: true });

    const changeAddress = () => {
      user.address.city = 'Los Angeles';
    };

    return {
      user,
      changeAddress
    };
  }
};
  • 立即执行回调
import { ref, watch } from 'vue';

export default {
  setup() {
    const count = ref(0);

    watch(count, (newValue, oldValue) => {
      console.log(`Count changed from ${oldValue} to ${newValue}`);
    }, { immediate: true });

    const increment = () => {
      count.value++;
    };

    return {
      count,
      increment
    };
  }
};

3.3 computed

在 Vue 3 中,computed 是一个独立的 API,可以在组合式 API 中使用
在 Vue 2 中,计算属性是在组件选项中定义的

Vue2的用法:

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    };
  },
  computed: {
    doubleCount() {
      return this.count * 2;
    }
  }
};
</script>

Vue3:

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
  </div>
</template>

<script>
import { ref, computed } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const doubleCount = computed(() => count.value * 2);

    return {
      count,
      doubleCount
    };
  }
};
</script>

带有 Getter 和 Setter 的计算属性
Vue2:

<template>
  <div>
    <p>Full Name: {{ fullName }}</p>
    <input v-model="firstName" placeholder="First Name">
    <input v-model="lastName" placeholder="Last Name">
  </div>
</template>

<script>
export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    };
  },
  computed: {
    fullName: {
      get() {
        return `${this.firstName} ${this.lastName}`;
      },
      set(newValue) {
        const names = newValue.split(' ');
        this.firstName = names[0];
        this.lastName = names[1] || '';
      }
    }
  }
};
</script>

Vue3:

<template>
  <div>
    <p>Full Name: {{ fullName }}</p>
    <input v-model="firstName" placeholder="First Name">
    <input v-model="lastName" placeholder="Last Name">
  </div>
</template>

<script>
import { ref, computed } from 'vue';

export default {
  setup() {
    const firstName = ref('John');
    const lastName = ref('Doe');

    const fullName = computed({
      get() {
        return `${firstName.value} ${lastName.value}`;
      },
      set(newValue) {
        const names = newValue.split(' ');
        firstName.value = names[0];
        lastName.value = names[1] || '';
      }
    });

    return {
      firstName,
      lastName,
      fullName
    };
  }
};
</script>