前言
对于Vue2转Vue3的项目中,此文了解所必备的特性知识点
1. 基本知识
Vue 3 引入了组合式 API(Composition API),这是一个强大的新功能,与传统的选项式 API(Options API)相比,提供了更灵活、更易于复用的方式来组织和复用代码
- setup 函数
组合式 API 的核心
每个组件的 setup 函数在组件实例创建之前执行,用于定义组件的响应式状态和逻辑
setup 函数接收两个参数:props
和context
props
::组件的属性对象context
:包含 attrs, slots, 和 emit 的对象(详细分析Vue3中的emit用法(子传父))
- 响应式数据
reactive
:创建一个响应式对象(详细分析Vue3中的reactive(附Demo))ref
:创建一个包含单一值的响应式引用(详细分析Vue3中的ref(附Demo))
- 计算属性
computed
:创建一个计算属性,依赖的值变化时自动更新
- 侦听器
watch
:侦听响应式数据的变化并执行副作用watchEffect
:立即执行传入的副作用函数,并在其依赖项发生变化时重新执行
- 生命周期钩子
在组合式 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.js' : '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>