Vue - 响应式原理(二)

引言

之前了解了什么是副作用函数与Vue2、Vue3实现响应式数据的不同点,并且实现了最基础的响应式,但是也存在一些问题,今天解决一下,effect固定,与无法与属性绑定的问题

副作用函数改进

之前是直接定义的一个effect函数,也导致了他是固定的,改进如下

let activeEffect: Function
const effect = (fn: Function) => {
  activeEffect = fn
  fn()
}

这个函数,接受一个函数作为参数,函数内部中将传入的fn赋值给外部作用域变量activeEffect,并且调用一下作为函数的第一次执行,通过这种方式解决effect固定的问题

buket的改进

之前用一个Set来作为存放effect的桶,问题是无法根据代理的对象属性值来进行对应,需要一个以键值对形式来存数据的数据结构。调用上面创建副作用函数如下:

effect(() => {
  app.innerHTML = `<div>name: ${proxy.name}: age: ${proxy.age}</div>`
})

这段代码中有三个重要的对象:

  1. 操作的代理对象
  2. 代理对象使用到的属性
  3. 副作用函数本身

其存在以下关系图

其中

  1. target代表代理的原始对象
  2. key代表操作的属性
  3. effect代表副作用函数

基本关系图在这里插入图片描述在这里插入图片描述
其关系是一个树形数据结构,之前的Set不符合这种场景,改进为WeakMap来做为buket

const buket = new WeakMap()

代理对象Getter/Setter的改进

  1. Getter中存储三者关系
  2. Setter中取出effect并执行

完整代码如下

const bukets = new WeakMap<typeof data, Map<Keys, Set<Function>>>()
const data = {
  name: "sakurige",
  age: 18,
}
type Keys = keyof typeof data

let activeEffect: Function
const effect = (fn: Function) => {
  activeEffect = fn
  fn()
}
const proxy = new Proxy(data, {
  get(target, key: Keys) {
    // 如果没有副作用函数,直接返回key对应的值
    if (!activeEffect) return target[key]
    let depsMap: Map<Keys, Set<Function>> | undefined = bukets.get(target)
    // 不存在对应的depsMap,则初始化,并重新赋值depsMap: 是一个Map类型
    if (!depsMap) bukets.set(target, (depsMap = new Map()))
    let effects: Set<Function> | undefined = depsMap.get(key)
    // 不存在key对应的effects,则初始化,并重新赋值effects:是一个Map类型
    if (!effects) depsMap.set(key, (effects = new Set()))
    effects.add(activeEffect)
    return target[key]
  },
  set(target, key: Keys, value) {
    ;(target[key] as any) = value
    bukets
      .get(target)
      ?.get(key)
      ?.forEach((fn) => fn())
    return true
  },
})

const app = document.querySelector("#app")!
const h1 = document.createElement("h1")
const h2 = document.createElement("h2")
const h3 = document.createElement("h3")
app.append(h1, h2, h3)
effect(() => {
  h1.innerHTML = `<div>my age: ${proxy.age}</div>`
})
effect(() => {
  h2.innerHTML = `<div>my name: ${proxy.name}</div>`
})
effect(() => {
  h3.innerHTML = `<div>my name: ${proxy.name} is ${proxy.age}</div>`
})
const btn1 = document.querySelector(".btn1")!
const btn2 = document.querySelector(".btn2")!
btn1.addEventListener("click", () => {
  proxy.age++
})
btn2.addEventListener("click", () => {
  proxy.name = "zs" + new Date().getSeconds()
})

在这里插入图片描述
改变age标题1,3发生改变,改变name标题2,3发生改变

代码整理

上面对之前的响应式进行了改造,基本功能实现,接下来整理一下代码:

  1. 收集副作用函数的代码提取
  2. 触发副作用函数的代码提取
// track 收集副作用函数
const track = (target: typeof data, key: Keys) => {
  // 判断是否存在activeEffect
  if (!activeEffect) return //没有则直接返回
  // 有则先获取target对应的依赖Map
  let depsMap: Map<Keys, Set<Function>> | undefined = bukets.get(target)
  if (!depsMap) bukets.set(target, (depsMap = new Map())) // 不存在则创建
  // 根据key获取key对应的set
  let deps: Set<Function> = depsMap.get(key)!
  if (!deps) depsMap.set(key, (deps = new Set()))
  deps.add(activeEffect)
}
// trigger 触发副作用函数
const trigger = (target: typeof data, key: Keys) => {
  const deps = bukets.get(target)
  if (!deps) return
  deps?.get(key)?.forEach((effect: Function) => effect())
}
// setter getter 函数
const proxy = new Proxy(data, {
  get(target, key: Keys) {
    track(target, key)
    return target[key]
  },
  set(target, key: Keys, value) {
    ;(target[key] as any) = value
    trigger(target, key)
    return true
  },
})

结束语

这次对之前的基础版响应式进行了改进,但是并未细说MapSetWeakMap之前的区别与用法,后续更在Es6的知识当中,当然现在的响应式也不是完善的,还需要进行更一步改进喽,后续进行学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值