目录
响应式的使用
参考深入响应式系统,响应式的使用:
import { ref, watchEffect } from 'vue' const count = ref(0) watchEffect(() => { document.body.innerHTML = `计数:${count.value}` }) // 更新 DOM count.value++
直白理解响应式,就是更新一个变量,UI也会自动更新,这样就取代了Jquery的手动更新UI,开发程序的时候,先关注UI和变量的连结,然后只需关注数据结构和逻辑就行了。
响应式的实现逻辑
观察上面的代码,可以看到,先使用ref函数包裹一个变量,然后使用watchEffect函数绑定变量和函数的对应关系。底层逻辑肯定是ref函数返回一个代理对象,代理对象的set方法中,会调用watchEffect函数中绑定的函数,肯定需要一个数据结构来维护代理对象和函数的关系,深入响应式系统也是这么干的:
副作用订阅将被存储在一个全局的 WeakMap>> 数据结构中。如果在第一次追踪时没有找到对相应属性订阅的副作用集合,它将会在这里新建。这就是 getSubscribersForProperty() 函数所做的事。
function reactive(obj) { return new Proxy(obj, { get(target, key) { track(target, key) return target[key] }, set(target, key, value) { target[key] = value trigger(target, key) } }) } function track(target, key) { if (activeEffect) { const effects = getSubscribersForProperty(target, key) effects.add(activeEffect) } }
这里令人好奇的是,并没有声明变量和watchEffect中的函数的对应关系,它们是怎么绑定的呢?耐心往下看。
代码实现
这里比较搞脑子的地方在于,调用simpleReactive去包装代理对象的时候,并没有绑定变量和函数的对应关系。直到调用effect函数中,触发对象的get方法,才真正绑定上。
// 保存代理对象上的 key 和对应的切面方法(effect中传入的函数) const targetMap = new Map();
既然它们使用get方法绑定的,那岂不是每次都要绑定?是在track方法中判断activeEffect==null
来避免。
function track(target, type, key) { if (activeEffect == null) return;
import Utils from './utils'; /** * 简单响应式 * 目标:创建响应式数据,也就是代理对象。代理对象的set方法上调用回调函数。关键是,如何得到对象的key和回调函数的对应关系(effect中传入的函数)。 * 1. reactive创建代理,但是还没有挂载回调函数到代理。 * 2. effect函数读取代理的key,并把effect中传入的函数挂载在key的set方法上。 * 3. 第二步的实现其实是,在代理函数中添加切面方法track,effect函数读取代理的key的时候,触发track方法。如此就获得了对象的key和回调函数(effect中传入的函数)的对应关系。 * // reactive创建代理 const ret = simpleReactive({ shoe: { size:10 }, sex: { male: 1, female: 0 } }) let val, val2 // effect 中通过读取ret.shoe这个key,触发代理对象的get方法。从而绑定这个函数到对应的key,用来在设置ret.shoe这个key的值的时候调用。 effect(() => { val = ret.shoe; var b = ret.shoe.size; var c = ret.sex.male; console.log(val); }) effect(() => { val2 = ret.shoe; var b = ret.shoe.size; var c = ret.sex.male; console.log(val2.size*4); console.log(ret.sex); }) setTimeout(function(){ ret.sex.male=1111; },1000); * */ // 保存代理对象上的 key 和对应的切面方法(effect中传入的函数) const targetMap = new Map(); // 保存已经 reactive 过的对象 const reactiveMap = new Map(); // effect 函数的临时保存 let activeEffect = null; function effect(fn) { try { activeEffect = fn; // effect 方法中,通过读取代理的 key,可以是多个 key,就不断触发代理的 get 方法,进而触发 track 方法,绑定回调函数 return fn(); } finally { // 避免重复绑定 activeEffect = null; } } function track(target, type, key) { if (activeEffect == null) return; console.log(`触发 track -> target: ${target} type:${type} key:${key}`); let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let deps = depsMap.get(key); if (!deps) { deps = new Set(); } if (!deps.has(activeEffect) && activeEffect) { deps.add(activeEffect); } depsMap.set(key, deps); } function trigger(target, type, key) { console.log(`触发 trigger -> target: ${target} type:${type} key:${key}`); const depsMap = targetMap.get(target); if (!depsMap) { return; } const deps = depsMap.get(key) if (!deps) { return; } deps.forEach((effectFn) => { effectFn(); }); } const handlers = { get(target, key, receiver) { track(target, 'get', key); const res = Reflect.get(target, key, receiver) if (Utils.isObject(res)) { // 返回值也是 object,递归调用 reactive return reactive(res); } return res; }, set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver) trigger(target, 'set', key) return result } } function reactive(target) { if (typeof target !== 'object') { console.warn(`reactive ${target} 必须是一个对象`); return target; } const existingProxy = reactiveMap.get(target) if (existingProxy) { return existingProxy; } const proxy = new Proxy(target, handlers); reactiveMap.set(target, proxy); return proxy; } export { reactive as simpleReactive, effect }
反馈:您觉得本站怎么样?(此评价不会公开,也不会对博主产生任何实际利益。)
- 非常优秀
- 可以
- 一般
- 垃圾
- 超级恶心
1 个评论