script setup 响应式:核心
- ref() ref 对象
- computed() 计算属性
- reactive() 响应式对象
- readonly() 只读对象
- watch() watch 侦听器
- watchEffect() watchEffect 侦听器
- watchPostEffect() watchPostEffect 侦听器
- watchSyncEffect() watchSyncEffect
- watch()、watchEffect() 的区别
1. ref() ref 对象
(1) 创建一个 ref 对象
(2) 参数:基本数据类型或复杂数据类型(复杂数据类型推荐使用 reactive)
(3) 返回值:一个响应式的、可更改的 ref 对象
(4) 此对象只有一个指向其内部值的属性 .value
(5) 可以为 .value 赋予新的值,它也是响应式的,即所有对 .value 的操作都将被追踪,并且写操作会触发与之相关的副作用
// 导入 ref函数
import { ref } from "vue";
// 创建一个 ref 对象 count
const count = ref<number>(0);
// 获取 count 的值
console.log(count.value);
// 修改 count 的值
count.value++;
2. computed() 计算属性
- 只读的计算属性 ref
- 参数:getter 函数
- 返回值:一个只读的响应式 ref 对象
- 可读可写的计算属性
- 参数:一个带有 get 和 set 属性的对象
- 返回值:一个可写的 ref 对象
- 通过
.value
获取计算属性的值
- 创建一个只读的计算属性
// 导入 ref 、computed 函数
import { ref, computed } from "vue";
// 创建一个只读的计算属性 ref:
const count1 = ref<number>(1);
const plusOne = computed<number>(() => count1.value + 1);
// 获取计算属性 plusOne 的值
console.log(plusOne.value); // 2
// 通过 getter函数 获得的计算属性值具有只读属性,不可修改
plusOne.value++; // 报错
- 创建一个可写的计算属性
// 导入 ref 、computed 函数
import { ref, computed } from "vue";
const count2 = ref<number>(1);
const plusTwo = computed<number>({
// get 属性值的函数需要有返回值,返回值就是该计算属性的 .value
get: () => count2.value + 1,
// set 属性值的函数只有在 计算属性 ref 被修改时才会触发
set: (val) => {
// val 是上次的 计算属性 的值
console.log(val); // 10
count2.value = val + 1;
},
});
// 修改计算属性,触发 set 属性值的函数
plusTwo.value = 10;
console.log(plusTwo.value); // 12
console.log(count2.value); // 11
3. reactive() 对象的响应式代理
- 响应式对象会深层地解包任何 ref 属性,同时保持响应性。
- 当访问某个响应式数组或 Map 这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包
- 返回的对象以及其中嵌套的对象都会通过 ES Proxy 包裹,因此不等于源对象
- 创建一个响应式对象
// 导入 ref 、 reactive 函数
import { ref, reactive } from "vue";
// 导入 Ref 泛型接口
import type { Ref } from "vue";
// 通过 reactive函数 创建一个响应式对象
const obj = reactive<{ count: number }>({
count: 0,
});
// 可以通过 对象.属性 来获取属性值
console.log(obj.count);
// 通过 ref函数 创建一个响应式对象
const obj1 = ref<{ count: number }>({
count: 1,
});
// 可以通过 对象.value.属性 来获取属性值
console.log(obj1.value.count);
// 响应式数组
const arr = reactive<Ref<string>[]>([ref("数组")]);
// 使用 数组[下标].value 获取元素的值
console.log(arr[0].value);
// 响应式Map
const map = reactive<Map<string, Ref<number>>>(new Map([["count", ref(0)]]));
// 这里需要 map.get(key).value 获取键值
console.log(map.get("count")?.value);
4. readonly() 只读代理
- 接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理
- 只读代理是深层的
- 只能读取,不能修改
import { ref, reactive, readonly } from "vue";
import type { Ref } from "vue";
const a1 = reactive<{ count: number }>({ count: 1 });
const copy1 = readonly<{ count: number }>(a1);
// 读取 copy 的属性值
console.log(copy1.count);
// 更改 copy 的属性值 将会失败,并会得到一个警告
copy1.count++; // 警告
const a2 = ref<{ count: number }>({ count: 2 });
const copy2 = readonly<Ref<{ readonly count: number }>>(a2);
// 读取 copy 的属性值
console.log(copy2.value.count);
// 更改 copy 的属性值 将会失败,并会得到一个警告
copy2.value.count++; // 警告
const a3: { count: number } = { count: 3 };
const copy3 = readonly<{ count: number }>(a3);
// 读取 copy 的属性值
console.log(copy3.count);
// 更改 copy 的属性值 将会失败,并会得到一个警告
copy3.count++; // 警告
5. watch() 侦听器
- 默认懒监听,只有当侦听源发生改变才会触发
- 参数一:侦听器的源。这个来源可以是以下几种:
- 一个函数,返回一个值
- 一个 ref
- 一个响应式对象
- …或是由以上类型的值组成的数组
- 参数二:发生变化时要调用的回调函数,回调函数接受三个参数
- 回调函数参数一:新值
- 回调函数参数二:旧值
- 回调函数参数三:用于注册副作用清理的回调函数
- 当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。
- 参数三:可选,配置对象,支持以下这些选项
- immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined。
- deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。
- flush:调整回调函数的刷新时机。
- onTrack / onTrigger:调试侦听器的依赖。
import { ref, watch } from "vue";
const count = ref<number>(0);
watch(
count, // 侦听的源
(newValue, oldValue) => {
// newValue:改变后的源值, oldValue:改变前的源值
console.log("新旧值", newValue, oldValue);
},
{
immediate: true, // 创建时立即触发调用
deep: true, // 深层侦听
// 'sync':源改变后立即调用,'post': 延迟到组件渲染后调用
flush: "post",
}
);
6. watchEffect() 立即执行一次的侦听器
- 与 watch 侦听器功能相似。 watch 函数默认情况下初始化时不调用; watchEffect 侦听器默认情况下会在初始化时调用一次
- 默认情况下,watchEffect 侦听器会在组件渲染之前执行一次
- 依赖更改时会被调用一次
- 参数、返回值
- 参数一:必需,函数,要运行的副作用函数
- 参数一的参数:可选,函数,用来注册清理回调
- 参数二:可选,对象,用来配置 调整副作用的刷新时机或调试副作用的依赖
- 返回值:函数,用来停止该副作用的函数
import { ref, reactive, watchEffect } from "vue";
const count = ref<number>(0);
// 默认情况下,侦听器将在组件渲染之前,立即运行一次
const stop = watchEffect(() => {
console.log(count.value); // -> 输出 0
});
// 监听到依赖改变,侦听器stop 重新执行一次
count.value++; // -> 输出 1
// 清除侦听器stop
stop();
// 参数二:配置对象,用来调整副作用的刷新时机或调试副作用的依赖
const stop2 = watchEffect(() => {}, {
flush: "post", // 侦听器延迟到组件渲染之后再执行
onTrack(e) {
debugger;
},
onTrigger(e) {
debugger;
},
});
stop2();
7. watchPostEffect()
- watchEffect() 使用 flush: ‘post’ 选项时的别名
- 只执行一次,等组件渲染之后执行一次
import { ref, watchPostEffect } from "vue";
const count = ref<number>(0);
watchPostEffect(() => {
console.log("post", count.value);
});
// 依赖改变,触发侦听器
count.value++;
8. watchSyncEffect()
- watchEffect() 使用 flush: ‘sync’ 选项时的别名
- 会执行两次,初始化时调用一次,依赖发生改变时立即调用一次
import { ref, watchSyncEffect } from "vue";
const count = ref<number>(0);
watchSyncEffect(() => {
// 组件初始化时,触发一次侦听器
console.log("Sync", count.value);
});
// 依赖改变,触发侦听器
count.value++;
9. watch、watchEffect 的区别
- 侦听源发生变化,触发回调函数
-
- watch 只追踪明确侦听的数据源,不会追踪在回调中访问到的东西
- watchEffect 会在副作用发生期间追踪依赖。会在同步执行过程中,自动追踪所有能访问到的响应式属性。
- 只要是 侦听的函数内部的变量 发生变化就会触发
-
- watch 可以输出侦听源变化前后的值
- watchEffect 不行
-
- watch 是 Vue2 的 API ,Vue3 中也保留了该 API
- watchEffect 是 Vue3 中新增的 API
-
- watch 默认情况下,组件初始化时不调用
- watchEffect 默认情况下,组件初始化时调用一次