ref is to solve the problem that proxy cannot directly proxy the original value. Let’s first look at the use of ref:
const name = ref('小黑子')
How is ref implemented? In fact, it is to "wrap" the original value with an object. Let’s take a look at the implementation of ref: The implementation of
function ref(val){ // 使用对象包裹原始值 const wrapper = { value:val } // 利用 reactive 将对象变成响应式数据 return reactive(wrapper) }
ref is that simple.
ref mainly does these two things in response to the original value:
1. Use an object to wrap the original value.
2. Use reactive to turn the wrapped object into responsive data.
We use ref to create a responsive object, but how do we distinguish whether an object is a normal object or a ref object? So our isref appears.
Let’s take a look at its use:
const name = ref('cj') console.log(isRef(name)); // true
So what is its implementation principle? The main implementation is still within the ref API. Let's take a look at the specific implementation code:
function ref(val){ const wrapper = { value:val } Object.defineProperty(warpper,'__v_isRef',{ value:true }) return reactive(wrapper) }
It turns out that a non-enumerable and non-writable attribute is added to the package object inside ref, and the value is true. This way we can check the attribute to determine if it is a ref.
function isRef(val) { return val.__v_isRef ?? false }
What is response loss? Response loss means that the responsive data is not responded to. Let’s look at the code below:
const obj = reactive({foo:1,bar:2}) const {foo,bar} = obj obj.foo++ // foo不会改变,还是 1
The obj above has lost its response, so it will not trigger re-rendering. Why is this so? In fact, because of the use of structure assignment, the expansion operator will also invalidate it.
const obj = reactive({foo:1,bar:2}) const newObj = {...obj} obj.foo++ // newObj.foo不会改变,还是 1
This is equivalent to redefining new data, which is no longer the original response data, and naturally does not have responsive capabilities.
toRef is to solve the problem of response loss. Let’s take a look at its implementation:
function toRef(obj,key) { const wrapper = { get value() { return obj[key] }, set value(val) { obj[key] = val } } Object.defineProperty(wrapper,'__v_isRef',{ value:true }) return wrapper }
Pass in two parameters, the first is responsive data, and the second is a key of obj.
The first part is to set up and declare an object. The object first sets the get value attribute. When accessing the toRef value, the attribute value corresponding to the incoming responsive data will be returned, and then The set with the value attribute is used to set the toRef value, and take the set new value to update the attribute value corresponding to the responsive data. In other words, the object returned by toRef is still the responsive data used.
The second part is used to set the returned data to be ref data. Because the data returned by toRef is similar to ref data, it is directly regarded as a ref data for the sake of unification.
The third part is to return the attribute object declared corresponding to the responsive data
In this way toRef solves the problem of response loss.
toRefs is to deconstruct the entire responsive object and make it responsive. The implementation code is as follows:
function toRefs() { const ret = {} for (const key in obj) { ret[key] = toRef(obj,key) } return ret }
Use a for loop to convert attributes one by one. Now let's take a look at the usage:
const obj = reactive({foo:1,bar:2}) const {foo,bar} = toRefs(obj) obj.foo++ // foo.value变为2了
When the attribute is a non-original value, it can still respond after deconstruction
const obj = reactive({foo:{age:18},bar:2}) const {foo,bar} = obj obj.foo.age++ // foo.age变为2了 obj.bar++ // bar没有改变,还是1
why is that? The reason is actually very simple, because the non-original value is assigned a reference address, which means that the destructured variable actually still points to the attribute of the original responsive data. The original value is a simple assignment and will not respond.
reactive 响应数据重新赋值后不再响应,ref 响应数据赋值后依然响应 let obj1 = reactive({foo:1,bar:2}) let obj2 = ref({foo:1,bar:2}) // 假如 obj1 与 obj2 直接展示在页面上 obj1 = {boo:2,far:3} // 页面 obj1 还是 {foo:1,bar:2} obj2.value = {boo:2,far:3} // 页面 obj2 变为 {boo:2,far:3} 了
What is the reason for this? Reactive reassignment response is lost, that is, a new object is reassigned, and it naturally becomes ordinary data and no longer responds. And ref can still respond because ref is set internally. The code is as follows:
function ref(val){ const wrapper = { value:val set value(val) { // isObject 这里代表判断是否是非原始值的一个方法 value = isObject(val) === 'Object' ? reactive(val) : val } } Object.defineProperty(warpper,'__v_isRef',{ value:true }) return reactive(wrapper) }
We understand, in fact, ref determines whether the new value set in set is a non-original value, and if so, calls reactive to turn it into responsive data.
When we use ref responsive data, we will always feel that .value is needed to obtain the value, which increases the mental burden on the user.
Is it possible to access the value directly instead of using .value?
In this way, you don’t need to care whether a certain data is ref data or whether you need to obtain the value through the value attribute.
This is where our unref comes into play. unref implements the ability to automatically remove ref. Automatic ref removal means that if the attribute read is a ref, the value attribute value corresponding to the ref will be directly returned.
Let’s take a look at the implementation of unref:
function unref(target) { return new Proxy(target,{ get(target,key,receiver) { const value = Reflect.get(target,key,receiver) return value.__v_isRef ? value.value : value }, set(target,key,newValue,receiver) { const value = target[key] if (value.__v_isRef) { value.value = newValue return true } return Reflect.set(target,key,newValue,receiver) } }) }
We found that unref internally uses Proxy to proxy the target object, receives an object as a parameter, and returns the proxy object of the object. When we access unref data, the get catcher is triggered, and then the catcher internally determines whether the incoming object is a ref object, and if so, the .value value of ref is returned directly. If not, the proxy object is returned directly.
When setting the value of the proxy object returned by unref, the set capturer is triggered. If the proxy object is ref, the new value that needs to be set is assigned to .value. If not, the assignment process is performed directly.
After we use ref to create responsive data and display it in the template, why not use .value?
{{ name }}
其实原因很简单,在组件 setup 中声明的 ref 响应式数据会传递给 unref 函数进行处理。所以在模板中访问 ref 的值,无需通过 value 属性来访问。
我们使用的 reactive 其实也是有 自动脱 ref 功能的,看一下下方例子:
const count = ref(0) const obj = reactive({conut}) obj.count // 0
我们可以看见 obj.count 是一个 ref 响应式数据。在 count 外层包裹一层对象,再传递给 reactive 后,再访问 obj.count 时就不需要再通过 value 属性访问值了。
也正是因为 reactive 内部也同样实现了自动脱 ref 的能力。
The above is the detailed content of vue3 original value response solution and how to solve the problem of response loss. For more information, please follow other related articles on the PHP Chinese website!