The core API of Proxy is dependent on the responsive principle of Vue3. Proxy can be used to intercept some object operations.
const obj = { a: 1 }; const p = new Proxy(obj, { get(target, property, receiver) { console.log("get"); return Reflect.get(target, property, receiver); }, set(target, property, value, receiver) { console.log("set"); return Reflect.set(target, property, receiver); }, has(target, prop) { console.log("has"); return Reflect.has(target, prop); }, deleteProperty(target, prop) { console.log("deleteProperty"); return Reflect.deleteProperty(target, prop); }, }); p.a; // 输出 --> get p.a = 2; // 输出 --> set "a" in p; // 输出 --> has delete p.a; // 输出 --> deleteProperty
As shown in the above example, we use Proxy to proxy the property access, property assignment, in operator, and delete operations of the Obj object, and perform console.log output.
Reflect is an API used in conjunction with Proxy. When we hijack certain operations, if we need to reflect these operations back, we need to Reflect this API.
Since we intercepted the operations of the object, the functions of these operations are lost. For example, accessing the attribute p.a should get the value of the a attribute, but there will be no results at this time. If we still If we want to have the function before interception, we need to use Reflect to reflect back.
const obj = { a: 1 }; const p = new Proxy(obj, { get(target, property, receiver) { console.log("get"); return Reflect.get(target, property, receiver); }, set(target, property, value, receiver) { console.log("set"); return Reflect.set(target, property, receiver); }, has(target, prop) { console.log("has"); return Reflect.has(target, prop); }, deleteProperty(target, prop) { console.log("deleteProperty"); return Reflect.deleteProperty(target, prop); }, });
We will use this example to describe the principle of Vue3 responsiveness throughout the following text.
<div id="app"></div> <script> // 创建一个响应式对象 const state = reactive({ counter: 1 }); // 立即运行一个函数,当响应式对象的属性发生改变时重新执行。 effect(() => { document.querySelector("#app").innerHTML = state.counter; }); // 2s 后视图更新 setTimeout(() => { state.counter += 1; }, 2000); </script>
We used reactive to create a reactive object state and called the effect method, which accepts a side effect function. The execution of effect will immediately call the side effect function and assign state.counter to #app. innerHTML; after two seconds, state.counter = 1. At this time, the side effect function of effect will be re-executed, and the page will become 2.
The internal execution process is roughly as shown in the figure below:
Call reactive() to return a Proxy proxy object and hijack the get and set operations of the object
Call effect( ) method, the property state.counter will be accessed, and the proxy's get operation will be triggered.
The get method will call track() to collect dependencies; establish a dependency relationship between an object (state), attribute (counter), and effect side effect function;
The set method will call trigger() to update dependencies; find the corresponding effect side effect function through the object (state) and attributes (coutner), and then re-execute it.
reactive will return the following Proxy object
const reactive = (target) => { return new Proxy(target, { get(target, key, receiver) { const res = Reflect.get(target, key, receiver); track(target, key); // 收集依赖 if (isObject(res)) { // 如果当前获取的属性值是一个对象,则继续将为此对象创建 Proxy 代理 return reactive(res); } return res; }, set(target, key, value, receiver) { Reflect.set(target, key, value, receiver); trigger(target, key); // 依赖更新 }, }); };
let activeEffect; function effect(fn) { const _effect = function reactiveEffect() { activeEffect = _effect; fn(); }; _effect(); }
Define a global activeEffect variable, which Points to the currently executing effect side effect function and keeps it updated. effect creates an internal side effect function for fn and then executes it immediately. At this time, the get operation of the object will be triggered and the track() method will be called.
effect(() => { // effect 的立即执行会访问 state.counter,触发了对象的 get 操作。 document.querySelector("#app").innerHTML = state.counter; });
track will create an object (state) => attribute (counter) => a dependency of effect
const targetMap = new WeakMap(); function track(target, key) { if (!activeEffect) { return; } let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = new Set())); } if (!dep.has(activeEffect)) { dep.add(activeEffect); } }
After the execution is completed, we get A data structure as follows:
[ // map 集合 { key: {counter: 1} // state 对象, value: [ // map 集合 { key: "counter", value: [ // set function reactiveEffect() {} // effect 副作用函数 ], } ], }, ];
Note: When we call effect, the current side effect function will be assigned to the global activeEffect, so we can correctly associate its dependencies at this time.
When we assign a value to state.counter, the set operation of the proxy object will be triggered, thereby calling the trigger method
setTimeout(() => { // 给 counter 属性赋值会触发 set 操作 state.counter += 1; }, 2000);
function trigger(target, key) { const depsMap = targetMap.get(target); if (!depsMap) return; const effects = depsMap.get(key); effects && effects.forEach((effect) => effect()); }
The above is the detailed content of What is the responsiveness principle of Vue3. For more information, please follow other related articles on the PHP Chinese website!