船长的航行日记
首页
  • 原生能力

    • JavaScript
    • Node
  • 框架能力

    • Vue
    • React
  • 学习笔记

    • 《TypeScript》学习笔记
    • 《JavaScript高程4》学习笔记
  • HTML
  • CSS
  • 最近在读
  • 奇思妙想
  • 读书收获
历史足迹
飞鸽传书
GitHub (opens new window)

captain

心之所向,海的彼岸
首页
  • 原生能力

    • JavaScript
    • Node
  • 框架能力

    • Vue
    • React
  • 学习笔记

    • 《TypeScript》学习笔记
    • 《JavaScript高程4》学习笔记
  • HTML
  • CSS
  • 最近在读
  • 奇思妙想
  • 读书收获
历史足迹
飞鸽传书
GitHub (opens new window)
  • JavaScript

  • node

  • vue组件库开发实践

  • react

  • 学习笔记

  • vue3源码

    • 如何阅读Vue3源码
    • 如何理解响应式
    • 实现手动执行的响应式
    • 实现自动执行的响应式
    • 实现对象多个属性对应的依赖的收集
    • 实现多个对象对应的依赖的收集
    • 实现ref
    • 优化
    • 总结
  • 前端乱炖
  • vue3源码
masongsong
2021-10-16
时间 3分钟
阅读量 0

实现ref

上篇文章实现多个对象对应的依赖的收集 (opens new window)已经实现了Vue3提供的响应式APIreactive,同时我们也发现了没有办法代理基本类型。也就是Vue3提供的另一个响应式相关的APIref,接下来我们来实现下:

// 基本类型的响应式API 类似于 Object.defineProperty
function ref(raw) {
  const target =  {
    get value() {
      console.log('基本类型----收集依赖')
      track(target, 'value')
      return raw
    },
    set value(newValue) {
      console.log('基本类型----触发依赖')
      raw = newValue
      trigger(target, 'value')
    }
  }
  return target
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

可以看到,其实ref的实现思路,也是类似于Object.defineProperty做的事情:

  • 设置set、get的拦截器。在读取属性的时候收集依赖,设置属性的时候触发依赖。只不过,key值统一都是value。

结合之前的实现,完整实现如下:


// 使用 WeakMap 描述多个对象的多个属性对应多个依赖的对应关系
const targrtMap = new WeakMap()

// 使用 activeEffect 变量保存当前激活的 effect
let activeEffect = null

// 引用类型的代理函数
function reactive(target) {
  const handler = {
    get: function(target, key, receiver) {
      const res = Reflect.get(target, key, receiver)
      // 收集依赖
      track(target, key)
      return res
    },
    set: function(target, key, value) {
      let oldValue = target[key]
      let res = Reflect.set(target, key, value)
      // 这里触发依赖
      if (res && oldValue !== value) {
        trigger(target, key)
      }
      return res
    }
  }
  const proxy = new Proxy(target, handler)
  return proxy
}

// 基本类型的响应式API 类似于 Object.defineProperty
function ref(raw) {
  const target =  {
    get value() {
      console.log('基本类型----收集依赖')
      track(target, 'value')
      return raw
    },
    set value(newValue) {
      console.log('基本类型----触发依赖')
      raw = newValue
      trigger(target, 'value')
    }
  }
  return target
}

// 依赖收集函数
function track(target, key) {
  if (!activeEffect) return
  let depsMap = targrtMap.get(target)
  if (!depsMap) {
    targrtMap.set(target, depsMap = new Map())
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, dep = new Set())
  }
  // 收集依赖
  dep.add(activeEffect)
}

// 触发依赖函数
function trigger(target, key) {
  let depsMap = targrtMap.get(target)
  if (!depsMap) return
  let dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => effect())
  }
}

// 副作用函数
function effect(eff) {
  activeEffect = eff
  activeEffect()
  activeEffect = null
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

我们写个例子测试一下:

// 测试例子
let total = 0
let total1 = 0
let discount = 0.8
let product = {
  price: 10,
  quantity: 2
}

let goods = {
  price: 10,
  quantity: 2
}

const proxyProduct = reactive(product)
const proxyGoods = reactive(goods)
const refDiscount = ref(discount)

// 这个就是副作用函数
effect(() => {
  total = proxyProduct.quantity * proxyProduct.price * (refDiscount.value)
})

effect(() => {
  total1 = proxyGoods.price * proxyGoods.quantity * (refDiscount.value)
})

proxyProduct.price = 500
proxyGoods.quantity = 500

console.log('product的total的值为:', total) // 800
console.log('goods的total1的值为:', total1) // 4000

// 修改了 discount 的代理值 此时也能自动执行了
refDiscount.value = 0.5

console.log('product的total的值为:', total) // 500
console.log('goods的total1的值为:', total1) // 2500

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

当我们修改refDiscount的值,也能执行依赖函数了。所以total和total1的值也相应的更新了。同时,Vue3提供的refAPI也支持对对象进行代理,我们前面已经实现了对象的代理函数reactive,所以实现思路也很简单,就是如果代理的是对象,就使用reactive函数进行代理:

const isObject = (val) => val !== null && typeof val === 'object

function ref(raw) {
  
  // 新增 如果是对象 就使用`reactive`函数
  if (isObject(raw)) {
    return reactive(raw)
  }
  const target =  {
    get value() {
      track(target, 'value')
      return raw
    },
    set value(newValue) {
      raw = newValue
      trigger(target, 'value')
    }
  }
  return target
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

然后我们将我们的测试例子修改下,都使用ref进行代理:

// 测试例子
let total = 0
let total1 = 0
let discount = 0.8
let product = {
  price: 10,
  quantity: 2
}

let goods = {
  price: 10,
  quantity: 2
}

const proxyProduct = ref(product)
const proxyGoods = ref(goods)
const refDiscount = ref(discount)

// 这个就是副作用函数
effect(() => {
  total = proxyProduct.quantity * proxyProduct.price * (refDiscount.value)
})

effect(() => {
  total1 = proxyGoods.price * proxyGoods.quantity * (refDiscount.value)
})

proxyProduct.price = 500
proxyGoods.quantity = 500

console.log('product的total的值为:', total) // 800
console.log('goods的total1的值为:', total1) // 4000

// 修改了 discount 的代理值 此时也能自动执行了
refDiscount.value = 0.5

console.log('product的total的值为:', total) // 500
console.log('goods的total1的值为:', total1) // 2500

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

可以看到,ref也正常代理了我们的引用类型。这样,我们就把Vue3中很重要的两个响应式API reactive、ref 给实现了,虽然我们的实现非常粗糙,但是和vue3的实现思路基本是一致的。如果你理解了之前几篇文章的实现,那么你去阅读Vue3的源码想必是有恍然大悟的感觉的,因为我们的实现基本覆盖了vue3响应式模块的主线思想。

编辑 (opens new window)
上次更新: 2021/10/30, 16:48:40
实现多个对象对应的依赖的收集
优化

← 实现多个对象对应的依赖的收集 优化→

最近更新
01
总结
10-19
02
优化
10-16
03
实现多个对象对应的依赖的收集
10-16
更多文章>
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 生命绿
  • 收获黄
  • 天空蓝
  • 激情红
  • 高贵紫