- 在
vue2
中如果新增一个功能,需要在data
中添加数据,在methods
或computed
或watch
中添加逻辑,那么数据与业务逻辑是分散的。 - 这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。
- 如果我们能够将与同一个逻辑关注点相关的代码配置在一起会更好。而这正是组合式 API 使我们能够做到的在
vue3
中,composition API
解决了数据与业务逻辑分散的问题。 setup()
函数是vue3
中专门新增的方法,可以理解为Composition Api
的入口,其中在setup
函数中由于此时还未创建vue
实例,因此是没有办法访问this
简单demo
- 下面是一个关于
composition api
的简单应用
<template>
<div>
<p>{{ count }}</p>
<p>{{ double }}</p>
<button @click="increase">add</button>
</div>
</template>
<script>
import { computed, ref } from 'vue';
export default {
setup() {
const count = ref(0);
const double = computed(() => {
return count.value * 2
})
function increase(){
count.value ++;
}
return {
count,
double,
increase
}
}
}
</script>
setup参数
props
:setup
函数中的第一个参数是props
。正如在一个标准组件中所期望的那样,setup
函数中的props
是响应式的,当传入新的prop
时,它将被更新context
:上下文对象,其中包含下面属性
setup(props, context) {
context.attrs
context.slots
context.parent
context.root
context.emit
context.refs
}
setup生命周期
选项式 API | Hook inside setup |
---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
- 当钩子在生命周期中被执行
export default {
setup() {
// mounted
onMounted(() => {
console.log('Component is mounted!')
})
}
}
响应式API
reactive
reactive
接受一个对象(json/arr
),返回一个响应式对象
<template>
<p>{{state.count}}</p>
<button @click="addCount">CountAdd</button>
</template>
export default {
setup () {
let obj = {
count: 1
};
let state = reactive(obj);
function addCount() {
state.count++;
console.log('state',state);
console.log('obj', obj);
}
return {
state,
addCount
}
}
}
- 注:
reactive
接受的参数必须是一个对象;
且通过reactive
创建的对象,当修改响应式数据之后,原对象也会发生变化
ref
在上面了解到,通过reactive
可以将一个普通对象转换为一个响应式对象,但如果需要将一个简单类型的数据转为响应式数据,可通过ref
实现
ref
的本质其实还是reactive
系统会根据我们给ref
传入的值将它转换为如下:ref(xxx) ----> reactive({value: xxx})
- 在
template
中使用ref
的值不需要通过value
获取,但在JS
中使用ref
的值必须通过value
获取
<template>
<p>{{count}}</p>
<button @click="addCount">CountAdd</button>
</template>
setup () {
let num = 4;
let count = ref(num);
function addCount() {
count.value++;
console.log('num',num);
console.log('count', count);
}
return {
count,
addCount
}
}
ref
与reactive
区别:
- 如果在
template
里使用的是ref
类型的数据,那么vue
会自动添加.value
- 如果在
template
里使用的是reactive
类型的数据,那么vue
不会自动添加.value
Vue
是如何决定是否需要自动添加.value
的
Vue
在解析数据之前,会自动判断这个数据是否是ref
类型的,如果是就自动添加.value
,如果不是就不自动添加.value
Vue
是如何判断当前的数据是否是Ref
类型的?
- 通过当前数据的
__v_ref
来判断的,由上图可以看到count
的__v_ref
的值为true
- 如果有这个私有的属性,并且取值为
true
,那么就代表是一个ref类型的数据
isRef
,isReactive
这两个方法可以对ref
和reactive
的数据类型进行判断
toRefs与toRef
toRefs()
函数可以将reactive()
创建出来的响应式对象,转换为普通对象,只不过这个对象上的每个属性节点,都是ref类型的响应式数据
<template>
<div>
<p>{{ count }}</p>
<button @click="increasement">add</button>
</div>
</template>
<script>
import { computed, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated, reactive, ref, toRefs } from 'vue';
export default {
setup(props, context) {
const state = reactive({
count: 0
})
const increasement = () => {
state.count ++;
}
return {
...toRefs(state),
increasement
}
}
}
</script>
toRef
:概念:为源响应式对象上的某个属性创建一个ref
对象,二者内部操作的是同一个数据值,更新时二者是同步的。相当于浅拷贝一个属性.
区别ref
: 拷贝的是一份新的数据单独操作,更新时相互不影响,相当于深拷贝。
场景:当要将某个prop
的ref
传递给某个复合函数时,toRef
很有用.
<template>
<div>
<p>{{ state.count }}</p>
<p>{{ count1 }}</p>
<p>{{ count2 }}</p>
<button @click="increasement">add</button>
</div>
</template>
<script>
import { computed, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated, reactive, ref, toRef, toRefs } from 'vue';
export default {
setup(props, context) {
const state = reactive({
count: 0
})
const count1 = toRef(state, 'count');
const count2 = ref(state.count);
const increasement = () => {
// state.count ++; // state中count改变时,count1也会改变
// count1.value ++; // count1改变时,state中count也会改变
count2.value ++; // 只有count2改变,count与count1均不会改变
}
return {
state,
count1,
count2,
increasement
}
}
}
</script>
computed
- 只读属性
import { ref, computed } from 'vue';
export default {
setup () {
const count = ref(0);
const double = computed(()=> count.value + 1);//1
double++;//Error: "double" is read-only
return {
count,
double
};
}
};
- 可读可写属性
// 创建一个 ref 响应式数据
const count = ref(1)
// 创建一个 computed 计算属性
const plusOne = computed({
// 取值函数
get: () => count.value + 1,
// 赋值函数
set: val => {
count.value = val - 1
}
})
// 为计算属性赋值的操作,会触发 set 函数
plusOne.value = 9
// 触发 set 函数后,count 的值会被更新
console.log(count.value) // 输出 8
watch
- 用于监听数据的变化
- 参数:
- 参数1:需要监听的数据项,类型需要是
ref
类型,一个reactive
对象,数组,或者getter/effect
函数 - 参数2:回调函数
- 选项对象,可以设置
immediate
与deep
- 参数1:需要监听的数据项,类型需要是
- 返回值:是取消监听的函数
setup(props, context) {
const state = reactive({
count: 0
})
// watch(state, (newVal, oldVal) => {
// console.log(newVal, oldVal);
// })
watch(() => state.count, (newVal, oldVal) => {
console.log(newVal, oldVal);
})
setInterval(() => {
state.count++;
}, 1000)
return {
state
}
}
- 监听清除: 在setup()函数内创建的watch监视,会在当前组件被销毁的时候自动停止。如果想要明确的停止某个监视,可以调用watch()函数的返回值即可
// 创建监视,并得到 停止函数
const stop = watch(() => {
/* ... */
})
// 调用停止函数,清除对应的监视
stop()
watchEffect
watchEffect
和watch
类似,可以监听属性的变化- 与
watch
的不同:watchEffect
不需要指定监听属性,可以自动收集依赖,只要我们回调中引用了响应式的属性,那么这些属性变更的时候,这个回调都会执行,而watch
只能监听指定的属性而做出变更(v3
中可以同时监听多个)watch
可以获取到新值和旧值,而watchEffect
获取不到watchEffect
会在组件初始化的时候就会执行一次与computed
同理,而收集到的依赖变化后,这个回调才会执行,而watch
不需要,除非设置了指定参数。
import { watchEffect, ref } from 'vue'
setup () {
const userID = ref(0)
watchEffect(() => console.log(userID))
setTimeout(() => {
userID.value = 1
}, 1000)
/*
* LOG
* 0
* 1
*/
return {
userID
}
}
文档参考:
Composition API详解