《Vuejs与实现》第 6 章(原始值响应式方案)
目录
6.1 引入 ref 的概念
6.2 响应丢失问题
6.3 自动解包 ref
6.4 总结
原始值指的是 Boolean、Number、 BigInt、String、Symbol、undefined 和 null 等类型的值
原始值是按值传递的,而非按引用传递。这意味着,如果一个函数接收原始值作为参数,那么形参与实参之间没有引用关系,它们是两个完全独立的值,对形参的修改不会影响实参。
Proxy 无法提供对原始值的代理,因此想要将原始值变成响应式数据,就必须对其做一层包裹。
6.1 引入 ref 的概念
Proxy的代理目标必须是非原始值,使我们无法拦截对原始值的操作。例如,我们无法阻止修改字符串:
let str = 'vue'
str = 'vue3' // 无法拦截这个操作
解决这个问题的方法是使用非原始值来“包裹”原始值,例如,我们可以用一个对象来包裹原始值:
const wrapper = {value: 'vue'
}
const name = reactive(wrapper) // 使用 Proxy 代理 wrapper,间接实现对原始值的拦截
name.value // vue
name.value = 'vue3' // 修改值可以触发响应
但这种方法存在两个问题:
- 用户需要创建一个包裹对象来创建响应式的原始值。
- 包裹对象的命名由用户定义,可能不规范,例如,可以使用 wrapper.value 或 wrapper.val。
为解决这些问题,我们可以封装一个函数,将包裹对象的创建工作封装在其中:
// 封装一个 ref 函数
function ref(val) {// 在 ref 函数内部创建包裹对象const wrapper = {value: val}// 将包裹对象变成响应式数据return reactive(wrapper)
}
上述代码,我们将创建 wrapper 对象的任务封装到 ref 函数内,然后使用 reactive 函数使其成为响应式数据并返回。这样,上述两个问题都得到了解决。
// 创建原始值的响应式数据
const refVal = ref(1)effect(() => {// 在副作用函数内通过 value 属性读取原始值console.log(refVal.value)
})
// 修改值能够触发副作用函数重新执行
refVal.value = 2
上述代码如期执行,但还有一个问题,如何区分 refVal 是原始值的包裹对象还是非原始值的响应式数据?例如:
const refVal1 = ref(1)
const refVal2 = reactive({ value: 1 })
上述代码 refVal1 和 refVal2 我们需要区分它们,因为这涉及到自动脱 ref 能力。
我们可以通过在