Vue 3.0 中的 reative
和 Vue 2.6 中提供的一个全局 API Vue.observable
雷同,都是用于让一个对象可响应,首先来比照一下他们之间的差别:
reactive | Vue.observable |
---|---|
基于 Proxy 实现 | 基于 Object.defineProperty 实现 |
对 代理对象 进行操作 | 间接操作 源对象 |
返回一个可响应的 代理对象 | 返回一个可响应的 源对象 |
reactivity工作流程
在 Vue 3.0 中, reactivity
被独立进去,没有任何依赖,能够用于任何想做响应式数据的中央
先抛开源码中实现的简单判断,来看一下他的次要工作流程
要害办法实现
本文次要实现了其中几个要害的办法:
reactive
- 创立响应式对象,在
Proxy
中定义get
及set
捕捉器,对传入的 源对象 的 代理对象 进行拦挡解决 get
捕捉到以后对象的属性也是对象,要进行递归- 定义基于
WeakMap
的reactiveMap
治理代理对象,如果传入的object
曾经有记录,间接返回 此对象 的 代理对象 ,如果没有,依照失常流程走
/** * 处理器对象,定义捕捉器 */const handlers = { set(target, key) { Reflect.set(...arguments) trigger(target, key) }, get(target, key) { track(target, key) return typeof target[key] === 'object' ? reactive(target[key]) : Reflect.get(...arguments) },}/** * 定义响应式对象,返回proxy代理对象 * @param {*} object */function reactive(object) { if (reactiveMap.has(object)) return reactiveMap.get(object) const proxy = new Proxy(object, handlers) reactiveMap.set(object, proxy) return proxy}
effect
- 副作用,创立用于治理
effect
的栈effectStack
,将effect
先入栈用于依赖收集,执行一次该effect
,进入get
捕捉阶段,捕捉结束之后进入finally
将其在栈中移出
/** * 副作用函数 * @param {*} fn */function effect(fn) { try { // 将须要执行的effect入栈,用于依赖收集过程中与key的关系对应 effectStack.push(fn) // 执行该effect,进入proxy的get拦挡 return fn() } finally { // 依赖收集结束及所有get流程走完,以后effect出栈 effectStack.pop() }}
track
effect
执行后数据触发get
捕捉器, 在此过程中调用track
进行依赖收集- 定义
targetMap
,以WeakMap
的形式收集依赖,治理指标对象target
及其对应的key
- 第二层用于治理
key
及其对应的effect
,下面流程图能够看到数据的构造和档次划分
/** * 依赖收集 * @param {*} target * @param {*} key */function track(target, key) { // 初始化依赖Map let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } // 第二层依赖应用Set寄存key对应的effect let dep = depsMap.get(key) if (!dep) { targetMap.get(target).set(key, (dep = new Set())) } // 取以后栈中的effect存入第二层依赖中 const activeEffect = effectStack[effectStack.length - 1] activeEffect && dep.add(activeEffect)}
trigger
- 批改在
effect
中指定过的内容时会触发set
捕捉器,在此过程中trigger
负责执行以后target
下key
对应的effect
,实现响应式的过程
/** * 触发响应,执行effect * @param {*} target * @param {*} key */function trigger(target, key) { const depsMap = targetMap.get(target) if (depsMap) { const effects = depsMap.get(key) effects && effects.forEach(run => run()) }}
computed
- 这里采纳简略粗犷的形式,间接返回一个
effect
/** * 计算属性 * @param {*} fn */function computed(fn) { return { get value() { return effect(fn) }, }}
成果展现
对象属性响应式,多层嵌套
const object = { o: { a: 1 }} const proxy = reactive(object)effect(() => { console.log(`proxy.o.a: ${proxy.o.a}`)})
- 首次调用打印一次,从新赋值后再次响应,调用
effect
响应式调色器
- 配置响应式对象,指定其rgb属性,顺便测一下
computed
const object = { r: 0, g: 0, b: 0}const proxy = reactive(object)const computedObj = computed(() => proxy.r * 2)effect(() => { const { r, g, b } = proxy document.getElementById('r').value = r document.getElementById('b').value = b document.getElementById('g').value = g document.getElementById( 'color' ).style.backgroundColor = `rgb(${r},${g},${b})` document.getElementById('color_text').innerText = `rgb:${r},${g},${b}` const { value } = computedObj document.getElementById( 'computed_text' ).innerText = `computed_text: r*2=${value}`})
- 拖动 rgb 3个 range 时各自的变动会体现在色彩块上
- 对象依赖关系
targetMap
构造如下
- reactiveMap 构造如下
总结
通过上述内容能够理解到 Vue 3.0 中的响应式原理
reactive
创立响应式对象effect
副作用,调用本身收集依赖,数据变更后从新调用该函数track
依赖收集trigger
触发依赖中对应的effect
computed
计算属性,对应属性值变更调用其effect
过程中还能更加相熟一些前置基础知识
- 代理与反射:
Proxy
Reflect
- JavaScript 规范内置对象:
WeakMap
Map
Set
- 语句执行应用技巧:
try
finally
- 代理与反射:
参考资料
- 残缺代码传送门
- Proxy
- Reflect
- WeakMap
- Map
- Set
- 语句执行
- Vue 3.0 源码