这里我们来讨论原始值的响应式方案,原始值主要指Boolean, Number, BigInt, String, Symbol, undefined和null等。因为原始值时按值传递的,如果函数接受原始值作为参数,那么形参和实参之间没有引用关系,是两个完全独立的值。此外JavaScript中的Proxy无法提供对原始值的代理。因此想要将原始值变成响应式数据,就必须做一层包裹,也就是ref
引入ref的概念
为了拦截对原始值的操作,可以使用一个非原始值去“包裹”原始值,例如用一个对象包裹原始值:
const wrapper = {
value: 'vue'
}
const name = reactive(wrapper)
name.value
name.value = 'vue3'
但这样做会导致两个问题
- 用户为了创建一个响应式的原始值,不得不顺带创建一个包裹对象
- 包裹对象由用户定义,会不规范,用户可以随意命名
因此可以封装一个函数,把包裹对象的创建工作都封装到该函数中
function ref(val){
const wrapper = {
value: val
}
return reactive(wrapper)
}
用下面的代码进行测试
const refVal = ref(1)
effect(()=>{
console.log(refVal.value)
})
refVal.value = 2
但是这样是有问题的,第一个问题是,如何区分refVal到底是原始值的包裹对象还是一个非原始值的响应式数据,如下图所示:
const refVal1 = ref(1)
const refVal2 = reactive({value:1})
refVal1和refVal2看不出有任何区别,但是,又有必要区分一个数据到底是不是ref,因为要具备自动脱ref能力 要区分一个数据是否是ref很简单,看下面代码:
function ref(val){
const wrapper = {
value: val
}
Object.defineProperty(wrapper,'_v_isRef', {
value: true
})
return reactive(wrapper)
}
使用Object.defineProperty为包裹对象wrapper定义一个不可枚举且不可写的属性,代表这个对象是一个ref,而非普通对象,这样可以通过检查_v_isRef属性来判断一个数据是否是ref了
知识扩展: Object.defineProperty Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。 语法: Object.defineProperty(obj, prop, descriptor) descriptor 要定义或修改的属性描述符。 value 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。 默认为 undefined。 更多请参考下面的链接 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
|