ref 全家桶
响应式数据/状态被直接设置为函数
ref(), reactive(),computed()
ref 替代 data 属性,类型是 refimpl
传入对象会让对象内所有属性均变为响应式,需要使用 .value 来访问/获取值
还是用 getter, setter 做的
因此 ref 和原值并不是一个值,相等会报错
reactive 只接受对象,类型是 proxy,效果和 ref 一样,但不使用 .value 语法取值,可以直接.语法访问
如果分别 log reactive 和 ref 值,则 reactive 直接返回 proxy,ref 需要.value 才返回 proxy
这个就完全是 proxy 了
ref 也会调用 reactive 来将所有数据转化为响应式的
ref 一般来说合适,而且数据分散好取
reactive 则适合一次性定义多个数据,存放配置属性或表单数据
computed() 函数值方法使用就行
watch,直接监听 ref 或者 ref.value 的值
直接监听 ref 就 watch(xxxref, (newVal, oldVal) => xxx),直接监听解包值报错
ref.value 就 watch(() => ref.value, (newVal, oldVal) => xxx),非要用回调使用解包值
监听对象属性也没啥区别,ref 正常.value,reactive 直接.属性
但是监听对象整体的话要注意一下,因为 watch 也是根据 对象引用来判断变化,所以一般情况下直接 watch 对象是没啥用的
- 一种是配置项设置 deep 为 true,但是返回的新旧值其实是修改前的同一个对象
为了获取修改前的值,需要返回对象的克隆, 对 ref.value 做展开语法即可监听
- 但这个只复制顶层属性,复制引用,所以还是回到深度克隆
监听多个响应数据
可以多个 watch,也可以按数组方式让 watch 监听多个 ref 或者多个回调 ref,但是监听回调只用写一个
watchEffect 和 watch
作用基本一样,只能获取修改后的值
watchEffect(callback(anyDataYouWantToWatch)),不需要像 watch 一样指定 ref,而是会自动判断
每次数据变化就会执行一次回调
watch 只有变化才执行回调,但 watchEffect 无论如何先执行一次
相当于 while 和 do-while
watch 等操作后的清理收尾
watchEffect 中可以引入 onInvalidate 参数
watch 要在第二个回调函数中引入 oninvalidate 参数
- javascript
watchEffect((onInvalidate) => { onInvalidate(() => { xxx; }); }); watch(() + options.value.title, (newVal, oldVal, onInvalidate) => {onInvalidate(() => {xxx})});
onValidate 在下次而非本次监听代码前执行
isRef
- 就是来判断响应式数据的,业务里用的不多
javascriptconst man = ref { name: "woodbell" }; const man2 = { name: "woodbell" }; const changeMan = () => { console.log(isRef(man)); console.log(isRef(man2)); };
shallowRef
- 相比 ref 可以做深层响应,shallowRef 只能做浅层响应
- 数据随便写,视图不更新
javascriptconst man3 = shallowRef({ name: "woodbell" }); //只能改到man3.value, 下面这个 man3.value.name就不是响应的 const change = () => { man3.value.name = "bellwood3"; }; //这样就算响应的 const change2 = () => { man3.value = { name: "bellwood3" }; };
triggerRef 决定了 ref 和 shallowRef 的更新
- 强制更新收集的依赖
- ref 可能就等于 shallowRef + triggerRef
- triggerRef 的调用是自动的,这就会有作用域级别的影响
- 下面这种写法就会导致 man3 作为 shallowRef 也一起更新
- 跨组件没啥问题
javascriptconst man = ref { name: "woodbell" }; const man3 = shallowRef({ name: "woodbell" }); const changeMan = () => { man.value.name='bellwood' man3.value.name = 'bellwood3' };
- 相当于直接指定 triggerRef(shallowRef)
javascriptconst man3 = shallowRef({ name: "woodbell" }); const changeMan = () => { man3.value.name = "bellwood3"; triggerRef(man3); };
customRef
- 自定义的 Ref
- 需要以函数形式 return,内容基本是指定 get 和 set
javascriptfunction MyRef<T>(value: T) { return customRef((track, trigger) => { return { get() { track(); }, set(newVal) { value = newVal; trigger(); }, }; }); }
ref 在元素中
- 可以直接获取 DOM
javascript<div ref="dom">这是DOM</div> const dom = ref<HTMLElement>(); //因为声明周期问题,函数里才能读到dom渲染之后的值 const change = () => { console.log(dom.value?.innerText); };
ref 源码
- ref 有一串重载,最后都是调用 createRef
- createRef 调用 isRef 进行判断,如果是就直接返回,不是就帮你创建 refimpl
- RefImpl 是一个类,我们定义的值变成 private,constructor 中又判断 value 是否是 shallow 的,是的话直接返回,不是的话调用 isReactive 做判断
- isReactive 又判断是不是 obj 类型,是就 reactive 包裹住返回,不是就直接返回 value
- refimpl 类的其他部分就是 get 和 set 代表的依赖收集和更新
- 可以看到,在依赖更新部分,ref 和 shallowRef 都调 triggerRef, triggerRef,调 triggerRefValue,triggerRefValue 又调 triggerEffect
reactive
- 和 ref 最主要的区别是只适用对象类型,源码上是因为做了泛型约束
- 以及读写值不需要加.value
- 不能直接=赋值或者说直接重写对象,会跳过 proxy 的拦截,还是要用自带的属性增减方法,比如对象包裹一层再用.语法,或者数组的 push 这些
let list = reactive<string[]>([]);
//这样就把list重写,响应性破坏了
const addList = () => {
setTimeout(() => {
let res = ["wood", "bell", "woodbell"];
list = res;
console.log(list);
}, 2000);
};
//要改成
const addList = () => {
setTimeout(() => {
let res = ["wood", "bell", "woodbell"];
list.push(...res)
console.log(list);
}, 2000);
};
//或者用对象包裹一层,但是这样会整体替换为一个数组,而不是三个数组元素, v-for也要替换成list.arr
let list = reactive<{arr:string[]}>({
arr:[]
})
const add = () => {
setTimeout(() => {
let res = ["wood", "bell", "woodbell"];
list.arr = res;
console.log(list.arr);
}, 2000);
}
readonly
- 将响应式对象变为只读的
- 只有在原对象变化时才会跟着变
- 实现父组件向子组件的单向数据流工具
- provide prop 时就可以包一层传
<div>
{{ read }}
</div>
<button @click.prevent="changeSync">改改readonly的原对象</button>
const readList = reactive({ name: "woodbell" });
const read = readonly(readList);
const changeSync = () => {
readList.name = "bellwood";
console.log(readList, read);
};
shallowReactive
- shallowRef 的 reactive
- 值可以随意写,但是视图不更新
- 和 reactive 一起被函数更改时,也同时触发 trigger 强制更新
- 作者回应是特性,重新渲染时会全局更新
<div>
{{ shallowObj }}
</div>
<button @click.prevent="changeShallow">改改shallowReactive</button>
const shallowObj = shallowReactive({
foo: {
bar: {
num: 1,
},
},
});
// 这样不更新视图
const changeShallow = () => {
shallowObj.foo.bar.num = 456;
console.log(shallowObj);
};
//改成这样就能更新
const changeShallow = () => {
shallowObj.foo= {
name:'bellwood'
}
console.log(shallowObj)
};
reactive 源码
- 首先是有对象类型的泛型约束
- 判断是否 isReadOnly 是则直接返回
- 不然正常 createReactiveObject
- 如果不是 isObject,直接返回
- 如果已经是 proxy(好像是几个内置 is-reactive 和 raw 变量),返回
- 如果能从 weakMap 构成的缓存 proxyMap 中找到,直接返回
- 如果在白名单 getTargetType 中找到,返回
- 最后才会包 proxy,然后更新 proxyMap 的内容
to 系列 toRef, toRefs,toRaw
- 修改响应式的值,非响应式的值变化,视图不变
- 从响应式对象中取值还能保持响应性,解构保持响应性
const man = { name: "woodbell", age: 25, like: "SSCI" };
const like = toRef(man, "like");
// man的数据会改,但视图不动
const change = () => {
// man.like = "CSSCI";
like.value = "CSSCI";
console.log(man);
};
// 改成响应式就能动
const man = reactive({ name: "woodbell", age: 25, like: "SSCI" });
- toRefs 源码
- 一次性返回一堆 ref
const toRefs= <T extends object>(object: T) => {
const map:any= {}
for(let key in object) {
map[key] = toRef(object, key)
}
return map
}
//这样解构就是普通值
const { name, age, like } = man;
const changeRefs = () => {
console.log(name, age, like);
};
// 这样解构就是ref对象
const { name, age, like } = toRefs(man);
toRaw 把响应式数据变成非响应的
- 相当于取出内部属性__v_raw
const man = reactive({ name: "woodbell", age: 25, like: "SSCI" });
const changeRaw = () => {
console.log(man, toRaw(man));
};
// 相当于
const changeRaw = () => {
console.log(man, man["__v_raw"]);
};
toRef, toRefs, toRaw 源码
首先调用 object[key]来取出 value
然后 isRef 判断是否为响应式,是就直接返回,不是就 ObjectRefImpl 包裹一下
这个新的 Impl 不收集依赖也不触发依赖更新,所以非响应式数据没啥用
Ref 的 impl 则是会收集依赖和触发依赖更新的
torefs 基本是遍历做上面的事情
toraw 就是读内置的 raw 属性
然后是组合式/函数式写法
let fullName = computed(() => firstName.value + "-" + lastName.value);
- 这种写法只能读取值,不能修改值,属性其实被 vue 处理为 readOnly
computed 源码
- 最开始还是一串函数重载,主要用来适配各种数据
- 如果 isFunction,就是只读的(函数式就是这样)
- 如果不是,则分别设置 getter 和 setter(选项式)
- 然后 getter,setter 传给 computedRefImpl
- 其中有 value 存值,以及 dirty 做脏值检查看看是否需要重新计算/还是适用缓存(vue 中默认值为 true),只有依赖变化,trigger 了才会变