vue3 的reactive简版响应式原理

375 阅读1分钟

vue3 的响应式原理

做响应式:

// 使用
let vdata = reactive(data)

vue2、vue3 的响应式区别:

1、vue2 (动态添加、删除属性 不行)

2、数组

3、初始化深层递归,速度会慢

4、新增的数据结构不支持 map、set

关于 proxy类的使用

先理解下述函数如何执行

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      const val = Reflect.get(obj, key)
      return typeof val === 'object' ? reactive(val) : val
    },
    set(target, key, val) {
      // console.log(`set: ${key}`)
      const ret = Reflect.set(target, key, val)
      return ret
    },
    deleteProperty(target, key) {
      // console.log(`delete: ${key}`)
      return Reflect.deleteProperty(target, key)
    },
  })
}

const state = reactive({
  msg: 'hello world',
  obj: { msg: 10 },
})

state.msg
state.foo = 'hello hui'
delete state.msg
console.log(state.msg)
console.log(state.obj.msg)
// get: msg
// set: foo
// delete: msg
// get: msg
// undefined
// get: obj
// get: msg
// 10

关于 reactive 代码实现

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      const val = Reflect.get(target, key)
      track(target, key)
      return typeof val === 'object' ? reactive(val) : val
    },
    set(target, key, val) {
      // console.log(`set: ${key}`) 
      // 出发trigger
      const ret = Reflect.set(target, key, val)
      trigger(target, key)
      return ret
    },
    deleteProperty(target, key) {
      // console.log(`delete: ${key}`)
      trigger(target, key)
      return Reflect.deleteProperty(target, key)
    },
  })
}

const effectStack = []
//映射表
const targetMap = new WeakMap()

function effect(fn) {
  const e = createReactiveEffect(fn)
  // 立即执行一次 处理错误 放入effectStack
  e()
  return e
}

function createReactiveEffect(fn) {
  const effect = function () {
    try {
      effectStack.push(effect) // 放入
      return fn() // 执行
    } finally {
      effectStack.pop()
    }
  }
  return effect
}

// 依赖收集  weekmap (对象映射 获取 map)   map(key值 映射 set)  set 是 收集的是effect事件
function track(target, key) {
  // 尝试获取effect函数
  const effect = effectStack[effectStack.length - 1] // 取最后一个
  if (effect) {
    let depMap = targetMap.get(target) // 获取map

    if (!depMap) {
      // 如果不存在, 第一次收集
      depMap = new Map()
      targetMap.set(target, depMap) // 不存在就塞进 targetMap
    }
    let deps = depMap.get(key) // 获取set
    if (!deps) {
      deps = new Set()
      depMap.set(key, deps)
    }
    deps.add(effect) // 收集依赖
  }
}

// 依赖触发
function trigger(target, key) {
  let depMap = targetMap.get(target) // 获取map
  if (depMap) {
    let deps = depMap.get(key)
    deps.forEach((fn) => {
      fn()
    })
  }
}

demo 测试:

const state = reactive({
  msg: 'hello world',
  obj: { msg: 10 },
})

effect(() => {
  state.msg
  console.log(state.msg)
})

state.msg = 'hello boy'

html 测试:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <div id="title"></div>
    </div>
    <script src="./index.js"></script>
    <script>
      const data = reactive({ title: 'vue3 响应式' })
      const dom = document.getElementById('title')
      effect(() => {
        dom.textContent = data.title
      })

      setInterval(() => {
        data.title = new Date().toString()
      }, 1000)
      
    </script>
  </body>
</html>

runtime-code 模块了解 watch、computed 实现