# Vue 3 响应式原理及实现

## 1. 实现响应式

### 响应基本类型变量

``````// 这种写成一行完全是为了节省空间，实际上我会一行一个变量
let a = 1, b = 2, c = a * b
console.log('c:' + c) // 2
a = 2
console.log('c:' + c) // 期望得到4``````

``````let a = 1, b = 2, c = a * b
console.log('c:' + c) // 2
a = 2
c = a * b
console.log('c:' + c) // 期望得到4``````

``````let a = 1, b = 2, c = 0
let effect = () => { c = a * b }
effect() // 首次执行更新c的值
console.log('c:' + c) // 2
a = 2
console.log('c:' + c) // 期望得到4``````

``````let a = 1, b = 2, c = 0
let effect = () => { c = a * b }

track() // 收集 effect
effect() // 首次执行更新c的值
console.log('c:' + c) // 2
a = 2
trigger() // a变化时，触发effect的执行
console.log('c:' + c) // 期望得到4``````

``````let a = 1, b = 2, c = 0
let effect = () => { c = a * b }

let dep = new Set()
let track = () => { dep.add(effect) }
let trigger = () => { dep.forEach(effect => effect()) }

track()
effect() // 首次执行更新c的值
console.log('c:' + c) // 2
a = 2
trigger() // a变化时，触发effect的执行
console.log('c:' + c) // 期望得到4，实际得到4``````

``````c: 2
c: 4``````

### 响应对象的不同属性

``````let obj = { a: 10, b: 20 }
let timesA = obj.a * 10
let divideA = obj.a / 10
let timesB = obj.b * 10
let divideB = obj.b / 10

// 100, 1, 200, 2
console.log(`\${timesA}, \${divideA}, \${timesB}, \${divideB}`)
obj.a = 100
obj.b = 200
// 期望得到 1000, 10, 2000, 20
console.log(`\${timesA}, \${divideA}, \${timesB}, \${divideB}`)``````

``````let depA = [
() => { timesA = obj.a * 10 },
() => { divideA = obj.a / 10 }
]
let depB = [
() => { timesB = obj.b * 10 },
() => { divideB = obj.b / 10 }
]``````

``````const depsMap = new Map() // 每一项都是一个 Set 对象
function track(key) {
let dep = depsMap.get(key)
if(!dep) {
depsMap.set(key, dep = new Set());
}
}

function trigger(key) {
let dep = depsMap.get(key)
if(dep) {
dep.forEach(effect => effect())
}
}``````

``````const depsMap = new Map() // 每一项都是一个 Set 对象
function track(key) {
...

// only for usage demo
if(key === 'a'){
}else if(key === 'b'){
}
}

function trigger(key) {
...
}

let obj = { a: 10, b: 20 }
let timesA = 0
let divideA = 0
let timesB = 0
let divideB = 0
let effectTimesA = () => { timesA = obj.a * 10 }
let effectDivideA = () => { divideA = obj.a / 10 }
let effectTimesB = () => { timesB = obj.b * 10 }
let effectDivideB = () => { divideB = obj.b / 10 }

track('a')
track('b')

// 为了省事直接改成调用trigger，后文同样
trigger('a')
trigger('b')

// 100, 1, 200, 2
console.log(`\${timesA}, \${divideA}, \${timesB}, \${divideB}`)
obj.a = 100
obj.b = 200

trigger('a')
trigger('b')
// 期望得到：1000, 10, 2000, 20 实际得到：1000, 10, 2000, 20
console.log(`\${timesA}, \${divideA}, \${timesB}, \${divideB}`)``````

### 响应多个对象

``````let obj1 = { a: 10, b: 20 }
let obj2 = { c: 30, d: 40 }
const targetMap = new WeakMap()

// 省略代码
// 获取 obj1 的 depsMap
// 获取 obj2 的 depsMap

targetMap.set(obj1, "obj1's depsMap")
targetMap.set(obj2, "obj2's depsMap")``````

targetMap WeakMap object depsMap
depsMap Map property dep
dep Set effect
• targetMap: 存放每个响应式对象（所有属性）的依赖项
• targetMap: 存放响应式对象每个属性对应的依赖项
• dep: 存放某个属性对应的所有依赖项（当这个对象对应属性的值发生变化时，这些依赖项函数会重新执行）

``````const targetMap = new WeakMap();

function track(target, key) {
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());
}
// 先忽略这个
}

function trigger(target, key) {
let depsMap = targetMap.get(target)
if(depsMap){
let dep = depsMap.get(key)
if(dep) {
dep.forEach(effect => effect())
}
}
}``````

``````const targetMap = new WeakMap();

function track(target, key) {
...

// only for usage demo
if(key === 'a'){
}
}

function trigger(target, key) {
...
}

let obj = { a: 10, b: 20 }
let timesA = 0
let divideA = 0
let effectTimesA = () => { timesA = obj.a * 10 }
let effectDivideA = () => { divideA = obj.a / 10 }

track(obj, 'a')
trigger(obj, 'a')

console.log(`\${timesA}, \${divideA}`) // 100, 1
obj.a = 100

trigger(obj, 'a')
console.log(`\${timesA}, \${divideA}`) // 1000, 10``````

## 2. Proxy 和 Reflect

• 当访问（`GET`）一个属性时，我们就调用 `track(obj, )` 自动收集依赖项（存储 `effect`
• 当修改（`SET`）一个属性时，我们就调用 `trigger(obj, ` 自动触发更新（执行存储的`effect`

Vue 2中我们使用 ES5 中的 `Object.defineProperty` 来拦截 `GET``SET`
Vue 3中我们将使用 ES6 中的 `Reflect``Proxy`。（注意：Vue 3不再支持IE浏览器，所以可以用比较多的高级特性）

• 使用 `.` => `obj.a`
• 使用 `[]` => `obj['a']`
• 使用 ES6 中的 `Reflect` => `Reflect.get(obj, 'a')`

### Proxy

``````let obj = { a: 1}
let proxiedObj = new Proxy(obj, {})
console.log(proxiedObj.a) // 1``````

`Proxy` 的第二个参数被称为 `handler``handler`就是包含捕捉器（trap）的占位符对象，即处理器对象，捕捉器允许我们拦截一些基本的操作，如：

• 查找属性
• 枚举
• 函数的调用

``````let obj = { a: 1}
let proxiedObj = new Proxy(obj, {
get(target, key) {
console.log('Get')
return target[key]
}
})
console.log(proxiedObj.a) // 1``````

``````let obj = {
a: 1,
get b() { return this.a }
}

let proxiedObj = new Proxy(obj, {
get(target, key, receiver) {
return target[key] // 这里的target是obj
}
})

let childObj = Object.create(proxiedObj)
childObj.a = 2
console.log(childObj.b) // 期望得到2 实际输出1``````

1. 当读取 `childObj.b` 时，`childObj` 上没有属性 `b`，因此会从原型链上查找
2. 原型链是 `proxiedObj`
3. 读取 `proxiedObj.b` 时，会触发`Proxy`捕捉器（trap）中的 `get`，这直接从原始对象中返回了 `target[key]`
4. 这里`target[key]``key` 是一个 `getter`，因此这个 `getter` 中的上下文 `this` 即为target，这里的 `target` 就是 `obj`，因此直接返回了 `1`

### Reflect

`Proxy``handler.get(target, prop, receiver)` 中的参数 `receiver``Proxy` 或者继承 `Proxy` 的对象。

`Reflect.get(target, prop, receiver)` 中的参数 `receiver` ：如果`target` 对象中指定了 `getter``receiver` 则为 `getter` 调用时的 `this` 值。

``````let obj = {
a: 1,
get b() { return this.a }
}

let proxiedObj = new Proxy(obj, {
get(target, key, receiver) {
// 这里的target是obj
return Reflect.get(target, key, receiver)
}
})

let childObj = Object.create(proxiedObj)
childObj.a = 2;
console.log(childObj.b) // 期望得到2 实际输出1``````

### 实现reactive函数

``````let obj = { a: 1}
let proxiedObj = new Proxy(obj, {
get(target, key, receiver) {
console.log('Get')
return Reflect.get(target, key, receiver)
}
set(target, key, value, receiver) {
console.log('Set')
return Reflect.set(target, key, value, receiver)
}
})
console.log(proxiedObj.a) // Get 1``````

``````function reactive(target) {
const handler = {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver)
}
}
return new Proxy(target, handler)
}``````

``````const reactiveHandler = {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver)
}
}

function reactive(target) {
return new Proxy(target, reactiveHandler)
}``````

``````let obj = reactive({ a: 10, b: 20 })
let timesA = 0
let divideA = 0
let effectTimesA = () => { timesA = obj.a * 10 }
let effectDivideA = () => { divideA = obj.a / 10 }

track(obj, 'a')
trigger(obj, 'a')

console.log(`\${timesA}, \${divideA}`) // 100, 1
obj.a = 100

trigger(obj, 'a')
console.log(`\${timesA}, \${divideA}`) // 1000, 10``````

``````const reactiveHandler = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
track(target, key)
return result
},
set(target, key, value, receiver) {
const oldVal = target[key]
const result = Reflect.set(target, key, value, receiver)

// 这里判断条件不对，result为一个布尔值
if(oldVal !== result){
trigger(target, key)
}
return result
}
}``````

``````let obj = reactive({ a: 10, b: 20 })
let timesA = 0
let divideA = 0
let effectTimesA = () => { timesA = obj.a * 10 }
let effectDivideA = () => { divideA = obj.a / 10 }

// 恢复调用 effect 的形式
effectTimesA()
effectDivideA()

console.log(`\${timesA}, \${divideA}`) // 100, 1
obj.a = 100

console.log(`\${timesA}, \${divideA}`) // 1000, 10``````

• `track` 函数中的 `effect` 现在还没处理，只能手动添加
• `reactive` 现在只能作用于对象，基本类型变量怎么处理？

## 3. activeEffect 和 ref

``````let obj = reactive({ a: 10, b: 20 })
let timesA = 0
let effect = () => { timesA = obj.a * 10 }
effect()

console.log(timesA) // 100
obj.a = 100
// 新增一行，使用到obj.a
console.log(obj.a)
console.log(timesA) // 1000``````

### 只响应需要依赖更新的代码（effect）

``````const targetMap = new WeakMap();
let shouldTrack = null
function track(target, key) {
if(shouldTrack){
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());
}

// 这里的 effect 为使用时定义的 effect
// shouldTrack 时应该把对应的 effect 传进来
// 如果有多个就手写多个
// ...
}
}``````

``````let shouldTrack = null
// 这里省略 track trigger reactive 代码
...

let obj = reactive({ a: 10, b: 20 })
let timesA = 0
let effect = () => { timesA = obj.a * 10 }
shouldTrack = effect // (*)
effect()
shouldTrack = null // (*)

console.log(timesA) // 100
obj.a = 100
console.log(obj.a)
console.log(timesA) // 1000``````

``````
let effect1 = () => { timesA = obj.a * 10 }
shouldTrack = effect1 // (*)
effect1()
shouldTrack = null // (*)

let effect2 = () => { timesB = obj.a * 10 }
shouldTrack = effect1 // (*)
effect2()
shouldTrack = null // (*)``````

``````let activeEffect = null
// 这里省略 track trigger reactive 代码
...

function effect(eff) {
activeEffect = eff
activeEffect()
activeEffect = null
}``````

``````function track(target, key) {
if(activeEffect){
...
// 这里不用再根据条件手动添加不同的 effect 了！
}
}``````

``````const targetMap = new WeakMap();
let activeEffect = null
function effect (eff) { ... }
function track() { ... }
function trigger() { ... }
function reactive() { ... }

let obj = reactive({ a: 10, b: 20 })
let timesA = 0
let timesB = 0
effect(() => { timesA = obj.a * 10 })
effect(() => { timesB = obj.b * 10 })

console.log(timesA) // 100
obj.a = 100
console.log(obj.a)
console.log(timesA) // 1000``````

#### 现阶段完整代码

``````const targetMap = new WeakMap();
let activeEffect = null
function effect(eff) {
activeEffect = eff
activeEffect()
activeEffect = null
}

function track(target, key) {
if(activeEffect){
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());
}
}
}

function trigger(target, key) {
let depsMap = targetMap.get(target)
if(depsMap){
let dep = depsMap.get(key)
if(dep) {
dep.forEach(effect => effect())
}
}
}

const reactiveHandler = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
track(target, key)
return result
},
set(target, key, value, receiver) {
const oldVal = target[key]
const result = Reflect.set(target, key, value, receiver)
if(oldVal !== result){
trigger(target, key)
}
return result
}
}

function reactive(target) {
return new Proxy(target, reactiveHandler)
}``````

### 实现ref

``````import { effect, reactive } from "./reactive"

let obj = reactive({ a: 10, b: 20 })
let timesA = 0
let sum = 0
effect(() => { timesA = obj.a * 10 })
effect(() => { sum = timesA + obj.b })

obj.a = 100
console.log(sum) // 期望: 1020``````

1. 直接给一个对象添加 `value` 属性
``````function ref(intialValue) {
return reactive({
value: intialValue
})
}``````
1. `getter``setter` 来实现
``````function ref(raw) {
const r = {
get value() {
track(r, 'value')
return raw
},
set value(newVal) {
raw = newVal
trigger(r, 'value)
}
}
return r
}``````

``````import { effect, reactive } from "./reactive"

function ref(intialValue) {
return reactive({
value: intialValue
})
}

let obj = reactive({ a: 10, b: 20 })
let timesA = ref(0)
let sum = 0
effect(() => { timesA.value = obj.a * 10 })
effect(() => { sum = timesA.value + obj.b })

// 期望: timesA: 100  sum: 120 实际：timesA: 100  sum: 120
console.log(`timesA: \${timesA.value}  sum: \${sum}`)

obj.a = 100
// 期望: timesA: 1000  sum: 1020 实际：timesA: 1000  sum: 1020
console.log(`timesA: \${timesA}  sum: \${sum}`)``````

Vue 3中的 `ref` 是用第二种方法来实现的，现在我们整理一下代码，把 `ref` 放到 `reactive.j` 中。

#### 现阶段完整代码

``````const targetMap = new WeakMap();
let activeEffect = null
function effect(eff) {
activeEffect = eff
activeEffect()
activeEffect = null
}

function track(target, key) {
if(activeEffect){
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());
}
}
}

function trigger(target, key) {
let depsMap = targetMap.get(target)
if(depsMap){
let dep = depsMap.get(key)
if(dep) {
dep.forEach(effect => effect())
}
}
}

const reactiveHandler = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
track(target, key)
return result
},
set(target, key, value, receiver) {
const oldVal = target[key]
const result = Reflect.set(target, key, value, receiver)
if(oldVal !== result){
trigger(target, key)
}
return result
}
}

function reactive(target) {
return new Proxy(target, reactiveHandler)
}

function ref(raw) {
const r = {
get value() {
track(r, 'value')
return raw
},
set value(newVal) {
raw = newVal
trigger(r, 'value)
}
}
return r
}``````

1. 根据定义，`ref` 应该只有一个公开的属性，即 `value`，如果使用了 `reactive` 你可以给这个变量增加新的属性，这其实就破坏了 `ref` 的设计目的，它应该只用来包装一个内部的 `value` 而不应该作为一个通用的 `reactive` 对象；
2. Vue 3中有一个 `isRef` 函数，用来判断一个对象是 `ref` 对象而不是 `reactive` 对象，这种判断在很多场景都是非常有必要的；
3. 性能方面考虑，Vue 3中的 `reactive` 做的事情远比第二种实现 `ref` 的方法多，比如有各种检查。

## 4. Computed

``````import { effect, reactive, ref } from "./reactive"

let obj = reactive({ a: 10, b: 20 })
let timesA = ref(0)
let sum = 0
effect(() => { timesA.value = obj.a * 10 })
effect(() => { sum = timesA.value + obj.b })``````

``````import { effect, reactive, computed } from "./reactive"

let obj = reactive({ a: 10, b: 20 })
let timesA = computed(() => obj.a * 10)
let sum = computed(() => timesA.value + obj.b)``````

### 实现computed

1. 返回响应式对象，也许是 `ref()`
2. 内部需要执行 `effect` 函数以收集依赖
``````function computed(getter) {
const result = ref();
effect(() => result.value = getter())
return result
}``````

``````import { effect, reactive, ref } from "./reactive"

let obj = reactive({ a: 10, b: 20 })
let timesA = computed(() => obj.a * 10)
let sum = computed(() => timesA.value + obj.b)

// 期望: timesA: 1000  sum: 1020 实际：timesA: 1000  sum: 1020
console.log(`timesA: \${timesA.value}  sum: \${sum.value}`)

obj.a = 100
// 期望: timesA: 1000  sum: 1020
console.log(`timesA: \${timesA.value}  sum: \${sum.value}`)``````

#### 现阶段完整代码

``````const targetMap = new WeakMap();
let activeEffect = null
function effect(eff) {
activeEffect = eff
activeEffect()
activeEffect = null
}

function track(target, key) {
if(activeEffect){
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());
}
}
}

function trigger(target, key) {
let depsMap = targetMap.get(target)
if(depsMap){
let dep = depsMap.get(key)
if(dep) {
dep.forEach(effect => effect())
}
}
}

const reactiveHandler = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
track(target, key)
return result
},
set(target, key, value, receiver) {
const oldVal = target[key]
const result = Reflect.set(target, key, value, receiver)
if(oldVal !== result){
trigger(target, key)
}
return result
}
}

function reactive(target) {
return new Proxy(target, reactiveHandler)
}

function ref(raw) {
const r = {
get value() {
track(r, 'value')
return raw
},
set value(newVal) {
raw = newVal
trigger(r, 'value')
}
}
return r
}

function computed(getter) {
const result = ref();
effect(() => result.value = getter())
return result
}``````

### 尚存问题

• 操作一些内置的属性，如 `Symbol.iterator``Array.length` 等触发了 `track` 如何处理
• 嵌套的对象，如何递归响应
• 对象某个 `key` 对应的 `value` 本身是一个 `reactive` 对象，如何处理