Composition API
1. reactive、ref 与 toRefs
在 vue2.x 中, 定义数据都是在data中, 但是 Vue3.x 可以使用reactive和ref来进行数据定义。
1.1 ref
一般用来定义一个基本类型的响应式数据
<template>
<h2>{{ num }}</h2>
<button @click="update">更新</button>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
// 定义响应式数据 ref对象
const num = ref(1)
// 更新响应式数据的函数
function update() {
num.value = num.value + 1
}
return {
num,
update
}
}
}
</script>
由此可以看出:
- 作用: 定义一个数据的响应式
- 语法: const xxx = ref(initValue):
创建一个包含响应式数据的引用(reference)对象
js 中操作数据: xxx.value
模板中操作数据: 不需要.value
1.2 reactive
- 作用: 定义多个数据的响应式
- const proxy = reactive(obj):
接收一个普通对象然后返回该普通对象的响应式代理器对象
响应式转换是“深层的”:会影响对象内部所有嵌套的属性
内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的。
<template>
<h2>name: {{ state.name }}</h2>
<h2>age: {{ state.age }}</h2>
<h2>wife: {{ state.wife }}</h2>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
/*
定义响应式数据对象
*/
const state = reactive({
name: 'tom',
age: 25,
wife: {
name: 'marry',
age: 22
}
})
return {
state
}
}
}
</script>
总结:
- ref和reactive是 Vue3 的 composition API 中 2 个最重要的响应式 API;
- ref 一般用来处理基本类型数据, reactive 用来处理对象(递归深度响应式);
- 如果用 ref 对象/数组, 内部会自动将对象/数组转换为 reactive 的代理对象;
- ref 内部: 通过给 value 属性添加 getter/setter 来实现对数据的劫持;
- reactive 内部: 通过使用 Proxy 来实现对对象内部所有数据的劫持, 并通过 Reflect 操作对象内部数据;
- ref 的数据操作: 在 js 中要.value, 在模板中不需要(内部解析模板时会自动添加.value)
1.3 toRefs
把一个响应式对象转换成普通对象,该普通对象的每一个属性(property)都是一个ref (都带有响应式)。
reactive的响应式功能是赋予对象的, 但是如果给对象解构或者赋值的时候, 会让数据丢失响应式功能。
使用toRefs 可以保证给该对象的每一个属性都是响应式的。 (每一个属性都添加了, ref 属性,都具有响应式)
<template>
<p>姓名: {{ nickname }}</p>
<p>年龄: {{ age }}</p>
</template>
<script>
import { defineComponent, reactive, ref, toRefs } from "vue";
export default defineComponent({
setup() {
const user = reactive({ nickname: "xiaofan", age: 26, gender: "女" });
return {
// 使用reRefs
...toRefs(user),
};
},
});
</script>
2. computed、watch、watchEffect
- computed函数:
与computed配置功能一致 - watch函数
与watch配置功能一致
监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
通过配置deep为true, 来指定深度监视 - watchEffect函数
不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
默认初始时就会执行第一次, 从而可以收集需要监视的数据
监视数据发生变化时回调
<template>
<input v-model="user.firstName" />
<input v-model="user.lastName" />
<input v-model="fullName1" />
<input v-model="fullName2" />
<input v-model="fullName3" />
</template>
<script lang="ts">
import { reactive, ref, computed, watch, watchEffect } from 'vue'
export default {
setup() {
const user = reactive({
firstName: 'A',
lastName: 'B'
})
// 只有getter的计算属性
const fullName1 = computed(() => {
console.log('fullName1')
return user.firstName + '-' + user.lastName
})
// 有getter与setter的计算属性
const fullName2 = computed({
get() {
console.log('fullName2 get')
return user.firstName + '-' + user.lastName
},
set(value: string) {
console.log('fullName2 set')
const names = value.split('-')
user.firstName = names[0]
user.lastName = names[1]
}
})
const fullName3 = ref('')
/*
watchEffect: 监视所有回调中使用的数据
*/
/*
watchEffect(() => {
console.log('watchEffect')
fullName3.value = user.firstName + '-' + user.lastName
})
*/
/*
使用watch的2个特性:
深度监视
初始化立即执行
*/
watch(
user,
() => {
fullName3.value = user.firstName + '-' + user.lastName
},
{
immediate: true, // 是否初始化立即执行一次, 默认是false
deep: true // 是否是深度监视, 默认是false
}
)
/*
watch一个数据
默认在数据发生改变时执行回调
*/
watch(fullName3, value => {
console.log('watch')
const names = value.split('-')
user.firstName = names[0]
user.lastName = names[1]
})
/*
watch多个数据:
使用数组来指定
如果是ref对象, 直接指定
如果是reactive对象中的属性, 必须通过函数来指定
*/
watch([() => user.firstName, () => user.lastName, fullName3], values => {
console.log('监视多个数据', values)
})
return {
user,
fullName1,
fullName2,
fullName3
}
}
}
</script>
由此可知watch和watchEffect的区别是:
- watch是惰性的,页面第一次加载时不触发watch函数,只有监听的数据发生变化时,才会触发watch函数;
- watch可以以数组的形式监听多个参数,如果多个数据同时发生改变,watch只触发一次 ;
- watch监听reactive数据时,是以()=>a 这个形式,目的是监听数据的getter函数,对于ref定义的数据,可以直接监听;
- watch可以获取监听的数据的新值和旧值;
- watchEffect函数,在页面第一次加载时就会触发,并且会一直监听追踪内部使用的响应式数据,只要追踪的响应式数据发生变化,watchEffect 都会运行
- watchEffect 也可以监听多个参数,只是不能监听对象,因为他无法监测对象内部的变化,可能是watchEffect无法实现深度监听吧。