vue3源码-如何在面试中写一个最小版的vue3-01

70 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 5 天,点击查看活动详情

结合前面几篇Vue3源码分析的文章,我们可以在面试时,给面试官实现一个mini版的Vue3,来展示对Vue3原理的了解程度。

这个 mini 版的Vue3,实现了 Vue3 核心的 Reactive 和 Effect 功能,代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<div id="app"></div>
<button id="btn">Add Age +1</button>

<body>
  <script>
    // 手写vue3响应式原理
    const obj = {
      name: 'name',
      age: 18,
      get aliasName() {
        console.log('who get aliasName:: ', this)
        return 'alias' + this.name // 不使用 Reflect 的话,this.name 不会再次走到 Proxy 的 get去取 name 的值
      }
    }

    /*
    step-1 -实现 reactive
    const p = reactive(obj)
    console.log('p.name', p.name)
    p.name = 'zhaoxueyu'
    console.log('p.aliasName', p.aliasName)
    */

    /**
     * step-2 -实现 Effect
     *
     */
    // 当前 Effect
    let activeEffect = null
    /* Effect */
    // effect
    class ReactiveEffect {
      constructor(fn) {
        this.fn = fn //用户函数
        this.active = true //是否激活
        this.deps = [] //依赖
      }
      run() {
        if (!this.active) {
          return this.fn()
        }
        activeEffect = this
        return this.fn()
      }
    }
    function effect(fn) {
      const _effect = new ReactiveEffect(fn)
      return _effect.run()
    }
    //依赖收集
    // targetMap 收集所有响应式对象的依赖
    /**
     * targetMap -> WeakMap{
     *  [state]: Map{
     *   name: Set[effect1, effect2, ...],
     *   age: Set[effect1, effect2, ...],
     *  }
     * }
    */
    const targetMap = new WeakMap()
    function track(target, key) {
      if (activeEffect) {
        // 从 targetMap 中找到传入的响应式对象
        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()))
        }
        let shouldTrack = dep.has(activeEffect)
        if (!shouldTrack) {
          dep.add(activeEffect)
        }
      }
    }
    //触发更新
    function trigger(target, key, value, oldValue) {
      const depsMap = targetMap.get(target)
      if (!depsMap) return
      const deps = depsMap.get(key)
      const effects = [...deps]
      effects && effects.forEach(e => {
        e.run()
      })
    }

    /* vue3 核心 -- reactive (由 Proxy 实现) */
    function reactive(target) {
      // 只代理对象
      if (!(target != null && typeof target === "object")) {
        return
      }
      const proxy = new Proxy(target, {
        get: function (target, key, receiver) {
          console.log('getter key:: ', key)
          // return target[key] 要使用 Reflect
          const res = Reflect.get(target, key, receiver)
          // 取值 触发依赖收集
          track(target, key)
          return res
        },
        set: function (target, key, value, receiver) {
          console.log('setter key:: ', key)
          // target[key] = value
          const old = target[key]
          const res = Reflect.set(target, key, value, receiver)
          //设置值时 触发更新
          if (old !== value) {
            trigger(target, key, value, old)
          }
          return res
        },
      })
      return proxy
    }



    const state = reactive(obj)
    effect(() => {
      console.log('user effect')
      app.innerHTML = '姓名:' + state.name + '年龄:' + state.age
    })
    btn.addEventListener('click', function () {
      state.age++
    })
  </script>
</body>

</html>

reactive 将数据变为响应式数据。

effect 最为响应式核心,负责收集依赖,更新依赖。computed 和 watch 等核心api,都是依靠它来实现的。

学会 reactive 和 effect 的实现将帮助你更好的了解 Vue3 源码。

祝各位读者面试成功!