- 该文章是在学习 小满vue3 课程的随堂记录
- 示例均采用
<script setup>
,且包含typescript
的基础用法
前言
本篇主要学习几个 api 及相关源码:
toRef
toRefs
toRaw
一、toRef
toRef(reactiveObj, key)
- 接收两个参数,第一个是
响应式对象
,第二个是指定的key
- 作用:将
响应式对象
的一部分也变为响应式
,通过.value
修改
- 接收两个参数,第一个是
- toRef 对
非响应式
对象无能为力,修改后视图不会更新
- 若
直接解构
reactiveObj,不使用 toRef,会使解构出的值丧失响应式
- 应用场景:某个单独的
属性key
需要被单独使用,并希望它是响应式的
① 对非响应式对象无能为力
<div class="">hobby:{{ hobby }}</div>
<button @click="change">修改</button>
// 普通对象
const man = {name: "xiaoman",age: 18,hobby: "ball",
};
const hobby = toRef(man, "hobby"); // 对普通对象使用,修改后仅修改值 但不更新视图const change = () => {hobby.value = "sing";console.log("hobby", hobby); // Ref<"sing">,但视图不更新
};
打印更新:
视图不更新:
② 将响应式对象的一部分也变为响应式
<div class="">hobby2:{{ hobby2 }}</div>
<button @click="change">修改</button>
// reactive 响应式对象
const man2 = reactive({name: "xiaoman",age: 18,hobby: "ball",
});
const hobby2 = toRef(man2, "hobby");const change = () => {hobby2.value = "dance";console.log("hobby2", hobby2, man2) // hobby2 和 man2 都会更新,视图也会更新
};
打印:
视图更新:
③ 直接解构响应式对象
若 直接解构
响应式对象,不使用 toRef,会使解构出的值 丧失响应式
// reactive 响应式对象
const man2 = reactive({name: "xiaoman",age: 18,hobby: "ball",
});
const { age } = man2; // 直接解构会丧失响应式
console.log("直接解构 age------", age);
解构出的就只是一个普通的值:
二、toRefs
toRefs(reactiveObj)
- 和 toRef 作用一样,只是不再指定某个key,而是
把全部属性都变为响应式
- 也是需要
传入响应式对象
,之后若对其解构,解构出的也是响应式对象
- 和 toRef 作用一样,只是不再指定某个key,而是
- 外层不再是响应式,内部的每一个key才是响应式
① 简单实现 toRefs 的源码
其实就是定义一个循环,循环体中 调用 toRef
const toRefsCopy = <T extends object>(obj: T) => {const map: any = {};for (let key in obj) {map[key] = toRef(obj[key]);}return map;
};
② toRefs 使用
<div>refs:{{ refs }}</div>
<div>refs2:{{ refs2 }}</div>
<button @click="change2">修改</button>
const blue = reactive({name: "blue",age: 19,
});const refs = toRefsCopy(blue);
const refs2 = toRefs(blue);
console.log("refs---", refs, refs2);const change2 = () => {// 解构出的每一个key都是响应式const { age } = refs2;age.value = 24;console.log("toRefs", refs, age);
};
toRefs 和 toRefsCopy 处理过后,每个key都是响应式:
直接解构出的 key 也是响应式,会立刻更新:
三、toRaw
toRaw(reactiveObj)
- 同样接收一个
响应式对象
- 作用:
toRaw
使响应式对象
变为普通原始对象
- 同样接收一个
- 取出
响应式对象
中__v_raw
对应的值,跟 toRaw 之后的结果相同(__v_raw 是源码内部的操作)
const people = reactive({name: "bill",age: 12,
});// 打印结果:people 是具有响应式的对象,toRaw 后就变成了普通原始对象
console.log("toRaw-------", people, toRaw(people));// 取出 __v_raw 对应的值,跟 toRaw 的结果相同
console.log("__v_raw-------", people["__v_raw"]);
四、源码学习
源码贴图:
源码理解记录:
/***(reactivity.cjs.prod.js)搜索 function toRef 即可找到** 1、function toRef (source, key, defaultValue)* - 先判断 isRef,true的话直接返回* - 再判断是不是函数类型,GetterRefImpl 内部仍然是直接返回,但会增加一些必要的标记(__v_isRef、__v_isReadonly)** - 再判断是不是object,是的话走进 propertyToRef* - 看 source[key] 是否满足 isRef,* true的话直接返回(已经设置过响应式了)* 否则走进 ObjectRefImpl,这就是 toRef 的核心方法* - ObjectRefImpl 与 RefImpl(ref) 内部同样有 get、set方法,* 但是区别在于 ObjectRefImpl 没有收集依赖(track)、触发更新(trigger) 的操作* 所以 toRef 对普通对象来讲没有响应式,只对已经有响应式的对象有用** - 上述类型都不属于的话,直接 ref(source)**** 2、function toRefs (object)** - 和上面自己写的 toRefsCopy 思路基本一致* - 先初始化一下,[]或者{}* - 然后循环,* 判断每个值 若 isRef=true 直接返回* 否则都 走进 ObjectRefImpl 中变为 ref 类型*** 3、function toRaw(observed)** - 判断 observed 是否存在 __v_raw ,存在的话继续递归 toRaw,否则直接返回 observed* - 取出的结果就是 不带 __v_raw 的原始普通对象***/