从 Vue2 到 Vue3 最明显的差异就是组合式API,使用组合式组合式API能够让代码更加优雅、有逻辑。
为什么要用组合式 API
Vue2 中我们写代码 Option API
- data
- 逻辑1数据状态
- 逻辑2数据状态
- 生命周期钩子
- 在 created 里执行逻辑1 逻辑2 相关代码
- 在 mounted 里执行逻辑1 逻辑2 相关代码
- computed
- 逻辑1相关的计算属性
- 逻辑2相关的计算属性
- watch
- 逻辑1相关的侦听
- 逻辑2相关的侦听
export default {
props: ['message'],
data() {
return {
count: 0, // 逻辑1的数据
name: 'lucy' // 逻辑2的数据
}
},
methods: {
onAdd() { // 逻辑1的⽅法
this.count++
},
getCount() { // 逻辑2的⽅法
this.count = Math.floor(Math.random() * 100)
}
},
computed: {
helloText() { // 逻辑2的计算属性
return this.message + ' ' + this.name
}
},
watch: {
count() { // 逻辑1的Watch
console.log('count 变了')
}
},
created() {
this.getCount() // 逻辑1的初始化操作
},
}
对应的同一业务的逻辑分散在 data、computed、watch 里,滚动条反复上下移动
- 问题:业务逻辑太分散,代码越多越看不懂
- 期望:同一业务逻辑的代码放到一起 使用组合式 API 可以让我们的代码组织的更加优雅,让相关功能的代码更加有序的组织到一起。
使用组合式 API 写代码
- 按需引入 ref、watch、生命周期钩子等
- 所有逻辑放⼊
setup函数,传入的第⼀个参数是props对象 - 通过
ref 、reactive 、toRef来创建响应式数据(原来是data里的)取值都是变量.value - watch、computed 是函数
- 生命周期钩子写法稍有区分,xxx 变成 OnXxx,如 mouted 变成 onMounted。不再需要created和 beforeCreate。
- 视图要用的变量为 setup 函数返回到对象属性,按需导出
<template>
<div>
<p>{{ count }}</P>
<button @click="onAdd">点我</button>
<p>{{ name }}</P>
<p>{{ helloText }}</P>
</div>
</template>
import { ref, watch, computed, onMounted } from 'vue'
export default {
props: ['message'],
setup(props) {
// 对 count 操作
let count = ref(0) // 响应式数据的初始值
const onAdd = () => {
count.value++
}
const getCount = () => {
count.value = Math.floor(Math.random() * 100)
}
watch(count, (val, oldVal) => {
console.log(`count 从${oldVal} 变成了 ${val}`)
})
onMounted(getCount)
// 对 name 操作
let name = ref('lucy')
let helloText = computed(() => {
props.message + ' ' + name.value
})
// 按需导出
return {
count,
name,
helloText
}
}
}
关于 setup
执行时机:在beforeCreate之前调用,发生在data、computed、methods被解析之前,所以无法在setup中获取this,组件实例没有被创建出来,this并没有指向当前组件实例,因此setup中不可以使用this。
关于生命周期
因为 setup 是围绕 beforeCreate 和 created ⽣命周期钩子运行的,所以不需要显式地定义它们。 换句话说, 在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。
| 选项式 API | 组合式 API |
|---|---|
| beforeCreate / created | 放在 setup 函数中 |
| beforeMount / mounted | onBeforeMount / onMounted |
| beforeUpdate / updated | onBeforeUpdate / onUpdated |
| beforeDestroy / destroyed | onBeforeUnmount / onUnmounted |
| activated / deactivated | onActivated / onDeactivated |
| errorCaptured | onErrorCaptured |
ref、reactive、toRefs、toRef
ref
接收一个基本类型数据,返回一个响应式 ref 对象,且只包含一个
.value属性。 语法:const xxx = ref(initValue)
- 创建一个响应式 ref 类型的对象
- js 中操作数据(setup内部):
xxx.value - 模板中操作数据(setup外部):
xxx不需要.value
该 ref 对象 count 内部指向 .value,我们可以在模板 template 里面直接使用 count,但是在 setup 里面通过count.value取值。
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
reactive
接收引用类型数据(普通对象),返回一个响应式代理对象。 语法:
const state = reactive(obj),state 是 Proxy 类型的对象
对象 state,使用参数state.foo、state.bar即可
const state = reactive({
foo: 1,
bar: 2
})
state.foo++
console.log(state.foo) // 2
console.log(fooRef.value) // 3
总结
- 用
reactive()创建的响应式对象,整个对象是响应式的,而对象里的每一项都是普通的值,解构赋值后,整个对象的普通值都不是响应式的。 - 而用
ref()创建的响应式的值,本身就是响应式的,并不依赖于其他对象。
toRefs
toRefs()可以将reactive()创建出来的响应式对象转换成内容为 ref 响应式的值的普通对象。
reactive 对象取出的每一项属性都是非响应式的,利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性
state 对象是响应式的,toRefs将 state属性都转换成 ref 形式的响应式数据stateAsRefs.foo 和 stateAsRefs.bar。
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
props是响应式的,直接解构 const { title } = props 会让 title 丧失响应特性,使用 toRefs 可以让解构后的 title 成为是响应式的。
import { toRefs } from 'vue';
export default {
props: {
title: String
}
setup(props) {
const { title } = toRefs(props);
console.log(title.value)
}
}
toRef
直接拿到响应式对象的属性保持响应特性。
toRef 是从响应式对象 state 里拿到属性 foo,使之变成一个响应式的数据 fooRef。
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3