Skip to content
js
const bucket = new Set()
const ref = (data) => {
  return new Proxy(data, {
    get(target, key) {
      bucket.add(effect) // 写死使用data数据的是 effect 函数
      return target[key]
    },

    set(target, key, newVal) {
      target[key] = newVal
      bucket.forEach(fn => fn()) // 全部执行
      return true
    }
  })
}

const data = { text: 'hello world' }
const refData = ref(data)
function effect() {
  const res = refData.text
  console.log(res) // 一般为副作用函数,操作dom,或其他共享状态
}

effect()
setTimeout(() => {
  refData.text = 'hello vue3'
}, 1000);

硬编码使用响应式数据(被自动执行)的是 effect名函数

副作用函数也存起来 createEffect

js
let activeEffect = null // <-- ✨ 临时变量 存需要记录的副作用函数(使用响应式数据方)
const createEffect = (fn) => {
  activeEffect = fn
  fn()
}

const bucket = new Set()
const ref = (data) => {
  return new Proxy(data, {
    get(target, key) {
      bucket.add(activeEffect) // 写死使用data数据的是 activeEffect 变量
      return target[key]
    },

    set(target, key, newVal) {
      target[key] = newVal
      bucket.forEach(fn => fn()) // 全部执行
      return true
    }
  })
}

const data = { text: 'hello world' }
const refData = ref(data)
function effect() {
  const res = refData.text
  console.log(res) // 一般为副作用函数,操作dom,或其他共享状态
}

createEffect(effect) // <-- ✨ 记录副作用函数(使用响应式数据方) 避免写死使用方函数名
setTimeout(() => {
  refData.text = 'hello vue3'
}, 1000);

此时响应式数据对应的使用方自动执行逻辑,并没用做到具体的属性对应具体的使用方(副作用)函数

我们捋一下他们之间的对应关系

一个响应式数据对象 对应多个属性 一个属性 对应多个使用方(副作用)函数

js
const bucket = new Map([
  [Obj, new Map([
    [ key, new Set([fn1, fn2]) ],
    // ... other key
  ])],
  // ...other Obj
])
js
let activeEffect = null // <-- ✨ 临时变量 存需要记录的副作用函数(使用响应式数据方)
const createEffect = (fn) => {
  activeEffect = fn
  fn()
}

const bucket = new Map()
const ref = (data) => {
  return new Proxy(data, {
    get(target, key) {

      const depsMap = bucket.get(target)
      if(!depsMap) {
        bucket.set(target, new Map())
      }

      const depsFns = depsMap.get(key) // <-- ❌ TypeError: Cannot read property 'get' of undefined
      if(!depsFns) {
        depsMap.set(key, new Set())
      }

      depsFns.add(activeEffect) // 写死使用data数据的是 activeEffect 变量
      return target[key]
    },

    set(target, key, newVal) {
      target[key] = newVal

      const depsMap = bucket.get(target)
      const depsFns = depsMap.get(key)
      depsFns.forEach(fn => fn()) // 全部执行
      
      return true
    }
  })
}

const data = { text: 'hello world' }
const refData = ref(data)
function effect() {
  const res = refData.text
  console.log(res) // 一般为副作用函数,操作dom,或其他共享状态
}

createEffect(effect) // <-- ✨ 记录副作用函数(使用响应式数据方) 避免写死使用方函数名
setTimeout(() => {
  refData.text = 'hello vue3'
}, 1000);

因为 const depsMap 被赋值为 undefined 了,即使后面马上设置了初始值,但那是 bucket 的值,因此要在get一次

这里这样简写👇

js
let depsMap = bucket.get(target)
if(!depsMap) {
  bucket.set(target, (depsMap = new Map()))
}

let depsFns = depsMap.get(key)
if(!depsFns) {
  depsMap.set(key, (depsFns = new Set()))
}

我们这样输出一个js赋值表达式: let a = 1; console.log((a = 2) 会得到赋值后 a 的值 --> 2

js
let activeEffect = null // <-- ✨ 临时变量 存需要记录的副作用函数(使用响应式数据方)
const createEffect = (fn) => {
  activeEffect = fn
  fn()
}

const bucket = new Map()
const ref = (data) => {
  return new Proxy(data, {
    get(target, key) {

      let depsMap = bucket.get(target)
      if(!depsMap) {
        bucket.set(target, (depsMap = new Map())) // <-- ✨ 初始化数据结构 同时赋值
      }

      let depsFns = depsMap.get(key)
      if(!depsFns) {
        depsMap.set(key, (depsFns = new Set()))
      }

      depsFns.add(activeEffect) // 写死使用data数据的是 activeEffect 变量
      return target[key]
    },

    set(target, key, newVal) {
      target[key] = newVal

      const depsMap = bucket.get(target)
      const depsFns = depsMap.get(key)
      depsFns.forEach(fn => fn()) // 全部执行

      return true
    }
  })
}

const data = { text: 'hello world' }
const refData = ref(data)
function effect() {
  const res = refData.text
  console.log(res) // 一般为副作用函数,操作dom,或其他共享状态
}

createEffect(effect) // <-- ✨ 记录副作用函数(使用响应式数据方) 避免写死使用方函数名
setTimeout(() => {
  refData.text = 'hello vue3'
}, 1000);
js
const map = new Map()
const weakmap = new WeakMap()

(() => {
  const foo = {foo:1}
  const bar = {bar:1}

  map.set(foo, 1)
  weakmap.set(bar, 1)
})()

不在代码中读取 foobar, 各map set时是最后一次读取,垃圾回收将开始清除

此时输出 mapweakmap 行为不相同

👇 使用 WeakMap 并 抽象出 追踪(track)触发(trigger) 函数

js
let activeEffect = null // <-- ✨ 临时变量 存需要记录的副作用函数(使用响应式数据方)
const createEffect = (fn) => {
  activeEffect = fn
  fn()
}

// ✨ 追踪 创建响应式数据和副作用函数的对应关系数据结构
const track = (target, key) => {
  let depsMap = bucket.get(target)
  if(!depsMap) {
    bucket.set(target, (depsMap = new Map())) // <-- ✨ 初始化数据结构 同时赋值
  }

  let depsFns = depsMap.get(key)
  if(!depsFns) {
    depsMap.set(key, (depsFns = new Set()))
  }

  depsFns.add(activeEffect) // 写死使用data数据的是 activeEffect 变量
}

// ✨ 触发 找到变动数据对应的副作用函数并执行
const trigger = (target, key) => {
  const depsMap = bucket.get(target)
  const depsFns = depsMap.get(key)
  depsFns.forEach(fn => fn()) // 全部执行
}

const bucket = new WeakMap() // ✨ WeakMap 外部业务代码不再使用响应式数据时自动回收
const ref = (data) => {
  return new Proxy(data, {
    get(target, key) {
      track(target, key) // ✨ 追踪 创建响应式数据和副作用函数的对应关系数据结构
      return target[key]
    },

    set(target, key, newVal) {
      target[key] = newVal
      trigger(target, key) // ✨ 触发 找到变动数据对应的副作用函数并执行

      return true
    }
  })
}

const data = { text: 'hello world' }
const refData = ref(data)
function effect() {
  const res = refData.text
  console.log(res) // 一般为副作用函数,操作dom,或其他共享状态
}

createEffect(effect) // <-- ✨ 记录副作用函数(使用响应式数据方) 避免写死使用方函数名
setTimeout(() => {
  refData.text = 'hello vue3'
}, 1000);