vue3简单实现

117 阅读1分钟
// index.js
function isObject(data) {
  return data && typeof data === 'object'
}
let targetMap = new WeakMap()
let activeEffect
/**
 * Map<target,DepsMap>
 * DepsMap<key,dep>
 * 
 * {
 *   target: {
 *     key: [effect, effect, effect, effect]
 *   }
 * }
 */
function track(target, key) { // dep.add
  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()))
  if (!dep.has(activeEffect)) dep.add(activeEffect) // Dep.target && dep.add(Dep.target)
}
function trigger(target, key) { // dep.notify
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  // e => effect
  depsMap.get(key).forEach(e => e && e())
}

// 在我们的 demo 里
// effect 执行 -> activeEffect 就有值了(更新页面)-> 触发 getter -> track() -> activeEffect 存起来了
// setter (count.value++) -> trigger -> activeEffect() -> 更新页面

// 总结一下:
// 收集副作用 -> 收集的时间(getter)-> 触发副作用执行(setter)
function effect(fn, options = {}) { // compiler + watcher
  const __effect = function(...args) {
    activeEffect = __effect
    return fn(...args) // this.cb()
  }
  if (!options.lazy) {
    __effect()
  }
  return __effect
}
/**
 * const a = reactive({ count: 0 })
 * a.count++
 */
export function reactive(data) {
  if (!isObject(data)) return
  return new Proxy(data, {
    get(target, key, receiver) {
      // 反射 target[key] -> 继承关系情况下有坑
      const ret = Reflect.get(target, key, receiver)
      track(target, key)
      return isObject(ret) ? reactive(ret) : ret
    },
    set(target, key, val, receiver) {
      Reflect.set(target, key, val, receiver)
      trigger(target, key)
      return true
    },
    deleteProperty(target, key, receiver) {
      const ret = Reflect.deleteProperty(target, key, receiver)
      trigger(target, key)
      return ret 
    }
  })
}
// 基本类型
/**
 * const count = ref(0)
 * count.value++
 */
export function ref(target) {
  let value = target
  const obj = {
    get value() {
      track(obj, 'value')
      return value
    },
    set value(newValue) {
      if (value === newValue) return
      value = newValue
      trigger(obj, 'value')
    }
  }
  return obj
}
export function computed(fn) {//只考虑函数的情况
  // 延迟计算 const c = computed(() => `${count.value} + !!!!`); c.value
  let __computed
  const run = effect(fn, { lazy: true })
  __computed = {
    get value() {
      return run()
    }
  }
  return __computed
}
export function mount(instance, el) {
  effect(function() {
    instance.$data && update(instance, el)
  })
  instance.$data = instance.setup()
  update(instance, el)
  function update(instance, el) {
    el.innerHTML = instance.render()
  }
}
// html
<!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>

  <script type='module'>
    import { mount, ref, reactive, computed } from './index.js'

    const App = {
      $data: null,
      setup() {
        let count = ref(0)
        let time = reactive({ seconds: 0 })
        let cc = computed(() => `computed : ${count.value + time.seconds}`)

        window.timer = setInterval(() => {
          time.seconds++
        }, 1000)


        setInterval(() => {
          count.value++
        }, 2000)

        return {
          count,
          time,
          cc
        }
      },
      render() {
        return `
          <h1>How Reactive?</h1>
          <p>this is reactive work: ${this.$data.time.seconds}</p>
          <p>this is ref work: ${this.$data.count.value}</p>
          <p>${this.$data.cc.value}</p>
        `
      }
    }

    mount(App, document.querySelector('#app'))
  </script>
</head>
<body>
  <div id='app'></div>
</body>
</html>

执行结果: image.png