+-

Vue 3.0 中的 reative 和 Vue 2.6 中提供的一个全局 API 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 结构如下


总结
通过上述内容可以了解到 Vue 3.0 中的响应式原理
reactive 创建响应式对象
effect 副作用,调用自身收集依赖,数据变更后重新调用该函数
track 依赖收集
trigger 触发依赖中对应的
effect
computed 计算属性,对应属性值变更调用其
effect
过程中还能更加熟悉一些前置基础知识
代理与反射:Proxy
Reflect JavaScript 标准内置对象:
WeakMap
Map
Set 语句执行使用技巧:
try
finally