实现一个vue3相对完善的响应式系统

主要实现
ref,toRef,toRefs,isRef,toRaw()
reactive ,shallowReactive(),shallowReadonly(),readonly()
watch,computed 等

// 存储副作用函数的桶
  const bucket = new WeakMap()
  const ITERATE_KEY = Symbol() //存储for...in副作用函数

  const reactiveMap = new Map() //每个对象,只能有一个reactive对象,存储这reactive

  function reactive(obj) {
    const proxy = createReactive(obj)

    const existionProxy = reactiveMap.get(obj) 
    if (existionProxy) return existionProxy // 如果已经存在reactive对象,就返回这个
//解决数组查找方法,中比较会创建新的的reactive对象,导致不相等

    reactiveMap.set(obj, proxy) //否则新建的存入reactiveMap

    return proxy
  }

  function shallowReactive(obj) {
    return createReactive(obj, true)
  }

  function readonly(obj) {
    return createReactive(obj, false, true)
  }

  function shallowReadonly(obj) {
    return createReactive(obj, true, true)
  }
  
  function isReactive(value) {
    if (isReadonly(value)) {
      return isReactive(value["__v_raw" /* RAW */]);
    }
    return !!(value && value["__v_isReactive" /* IS_REACTIVE */]);
  }
  function isReadonly(value) {
    return !!(value && value["__v_isReadonly" /* IS_READONLY */]);
  }
  function isShallow(value) {
    return !!(value && value["__v_isShallow" /* IS_SHALLOW */]);
  }
  function toRaw(observed) {
    const raw = observed && observed["__v_raw" /* RAW */];
    return raw ? toRaw(raw) : observed;
  }

  
  
  
  const arrayInstrumentations = {};


  // 重写数组查询方法
  ['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
    const originMethod = Array.prototype[method]
    arrayInstrumentations[method] = function(...args) {
      // this 是代理对象,先在代理对象中查找,将结果存储到 res 中
      let res = originMethod.apply(this, args)

      if (res === false) {
        // res 为 false 说明没找到,在通过 this.raw 拿到原始数组,再去原始数组中查找,并更新 res 值
        res = originMethod.apply(this.raw, args)
      }
      // 返回最终的结果
      return res
    }
  })


  let shouldTrack = true;

  // 重写数组修改方法

  ['push'].forEach(method => {
    const originMethod = Array.prototype[method]
    arrayInstrumentations[method] = function(...args) {
      shouldTrack = false  // 5.7.4避免多个effect中都是要push方法导致报错
      let res = originMethod.apply(this, args)
      shouldTrack = true
      return res
    }
  })

  function createReactive(obj, isShallow = false, isReadonly = false) {
    return new Proxy(obj, {
      // 拦截读取操作
      get(target, key, receiver) {
        console.log('get: ', key) 
        if (key === 'raw') { //获取原始值
          return target
        }

        if (Array.isArray(target) && arrayInstrumentations.hasOwnProperty(key)) {
          return Reflect.get(arrayInstrumentations, key, receiver)  // 重写数组方法
        }

        // 非只读的时候才需要建立响应联系
        if (!isReadonly && typeof key !== 'symbol') { // symbol表示如果是数组,是要for..of或者values,触发 ///symbol属性读取,不给它收集副作用函数
          track(target, key)
        }

        const res = Reflect.get(target, key, receiver)

        if (isShallow) {
          return res
        }

        if (typeof res === 'object' && res !== null) {
          // 深只读/响应
          return isReadonly ? readonly(res) : reactive(res)
        }

        return res
      },
      // 拦截设置操作
      set(target, key, newVal, receiver) {
        console.log('set: ', key)
        if (isReadonly) {
          console.warn(`属性 ${key} 是只读的`)
          return true
        }
        const oldVal = target[key]
        // 如果属性不存在,则说明是在添加新的属性,否则是设置已存在的属性
        const type = Array.isArray(target)
          ? Number(key) < target.length ? 'SET' : 'ADD' // 如果是数组,key小于原始的length是更新,大于是添加新属性
          : Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD' // 代理普通对象
        // 设置属性值
        const res = Reflect.set(target, key, newVal, receiver)
        if (target === receiver.raw) { // 优化!防止出现代理对象的原型也是代理对象,会多次执行副作用函数
          if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
            trigger(target, key, type, newVal)
          }
        }

        return res
      },
      has(target, key) {   //代理 in 
        track(target, key)
        return Reflect.has(target, key)
      },
      ownKeys(target) {             //代理for...in
        console.log('ownkeys: ')
        track(target, Array.isArray(target) ? 'length' : ITERATE_KEY) // 如果是数组,那么length值改变的时候,
        // 会重新执行for..in的副作用函数
        return Reflect.ownKeys(target)
      },
      deleteProperty(target, key) {  //代理删除对象属性
        if (isReadonly) {  // 只读不能删除
          console.warn(`属性 ${key} 是只读的`)
          return true
        }
        const hadKey = Object.prototype.hasOwnProperty.call(target, key)
        const res = Reflect.deleteProperty(target, key)

        if (res && hadKey) {
          trigger(target, key, 'DELETE')
        }

        return res
      }
    })
  }

  function track(target, key) {
    if (!activeEffect || !shouldTrack) return
    let depsMap = bucket.get(target)
    if (!depsMap) {
      bucket.set(target, (depsMap = new Map()))
    }
    let deps = depsMap.get(key)
    if (!deps) {
      depsMap.set(key, (deps = new Set()))
    }
    deps.add(activeEffect)
    activeEffect.deps.push(deps)
  }

  function trigger(target, key, type, newVal) {
    console.log('trigger', key)
    const depsMap = bucket.get(target)
    if (!depsMap) return
    const effects = depsMap.get(key)

    const effectsToRun = new Set()
    effects && effects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })

    if (type === 'ADD' || type === 'DELETE') { //普通对象新加和删除属性需要 执行for...in的副作用函数
      const iterateEffects = depsMap.get(ITERATE_KEY)
      iterateEffects && iterateEffects.forEach(effectFn => {
        if (effectFn !== activeEffect) {
          effectsToRun.add(effectFn)
        }
      })
    }

    if (type === 'ADD' && Array.isArray(target)) { // 数组新增值,要出发length的副总用函数
      const lengthEffects = depsMap.get('length')
      lengthEffects && lengthEffects.forEach(effectFn => {
        if (effectFn !== activeEffect) {
          effectsToRun.add(effectFn)
        }
      })
    }

    if (Array.isArray(target) && key === 'length') {  // 修改length要出发,超过新length的属性的副作用函数
      depsMap.forEach((effects, key) => {
        if (key >= newVal) {
          effects.forEach(effectFn => {
            if (effectFn !== activeEffect) {
              effectsToRun.add(effectFn)
            }
          })
        }
      })
    }

    effectsToRun.forEach(effectFn => {
      if (effectFn.options.scheduler) {
        effectFn.options.scheduler(effectFn)
      } else {
        effectFn()
      }
    })
  }

  // 用一个全局变量存储当前激活的 effect 函数
  let activeEffect
  // effect 栈
  const effectStack = []

  function effect(fn, options = {}) {
    const effectFn = () => {
      cleanup(effectFn)
      // 当调用 effect 注册副作用函数时,将副作用函数复制给 activeEffect
      activeEffect = effectFn
      // 在调用副作用函数之前将当前副作用函数压栈
      effectStack.push(effectFn)
      const res = fn()
      // 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并还原 activeEffect 为之前的值
      effectStack.pop()
      activeEffect = effectStack[effectStack.length - 1]

      return res
    }
    // 将 options 挂在到 effectFn 上
    effectFn.options = options
    // activeEffect.deps 用来存储所有与该副作用函数相关的依赖集合
    effectFn.deps = []
    // 执行副作用函数
    if (!options.lazy) {
      effectFn()
    }

    return effectFn
  }

  function cleanup(effectFn) { //解决分支问题,document.body.innerText = obj.ok ? obj.text : 'not' 这种情况下的优化
    for (let i = 0; i < effectFn.deps.length; i++) {
      const deps = effectFn.deps[i]
      deps.delete(effectFn)
    }
    effectFn.deps.length = 0
  }

  // =================================================================


  function ref(val) { // 原始值代理
    const wrapper = {
      value: val
    }

    Object.defineProperty(wrapper, '__v_isRef', {
      enumerable:false,
      value: true
    })

    return reactive(wrapper)
  }


  // 解决reactive对象可以是有展开符...不丢失响应
  function toRefs(obj) {
    const ret = {}
    for (const key in obj) { //批量转化为ref
      ret[key] = toRef(obj, key)
    }
    return ret
  }

  function toRef(obj, key) { //转换为ref的形式
    const wrapper = {
      get value() {
        return obj[key]
      },
      set value(val) { //结构的值可以设置
        obj[key] = val
      }
    }

    Object.defineProperty(wrapper, '__v_isRef', {
      value: true
    })

    return wrapper
  }

  //自动脱ref
  function proxyRefs(target) {
    return new Proxy(target, {
      get(target, key, receiver) {
        const value = Reflect.get(target, key, receiver)
        return value.__v_isRef ? value.value : value
      },
      set(target, key, newValue, receiver) {
        const value = target[key]
        if (value.__v_isRef) {
          value.value = newValue
          return true
        }
        return Reflect.set(target, key, newValue, receiver)
      }
    })
  }


  // =================================================================

  ///实现computed
  
  function computed(getter) {
    let value // 缓存值
    let dirty = true // true表示getter中的多个数据,改变了,需要重新计算

    const effectFn = effect(getter, {
      lazy: true,
      scheduler() {
       // console.log('执行')
        if (!dirty) {
          dirty = true
          trigger(newCompute, 'value') //解决computed的值在effect中能正常收集和触发,给computed设置响应式
        }
      }
    })

    const newCompute = {
      get value() { // 第一个获取computed值
        if (dirty) {
          value = effectFn()// getter收集到各自的副作用函数
          dirty = false
        }
        track(newCompute, 'value')// 用来收集计算属性的值的代理
        return value
      }
    }

    return newCompute // 第一次读取,触发
  }





  // 实现watch
  // =========================

  // 递归读取对象所有的属性
  function traverse(value, seen = new Set()) {
    if (typeof value !== 'object' || value === null || seen.has(value)) return
    seen.add(value)
    for (const k in value) {
      traverse(value[k], seen)
    }
    return value
  }


  function watch(source, cb, options = {}) {
    let getter
    // 如果是监听对象的函数,直接为getter,注意监听函数必须是读取数据
    if (typeof source === 'function') {
      getter = source
    } else {
      getter = () => traverse(source)
    }

    let oldValue, newValue

    let cleanup

    function onInvalidate(fn) { // 解决上一次的结果的取消,
      cleanup = fn
    }

    const job = () => {
      newValue = effectFn()
      if (cleanup) {
        cleanup()
      }
      cb(oldValue, newValue, onInvalidate)
      oldValue = newValue
    }

    const effectFn = effect(
      // 执行 getter
      () => getter(), // 副作用函数
      {
        lazy: true,
        scheduler: () => {   //异步执行,调度watch中回调函数的执行时机
          if (options?.flush === 'post') { //表示其他副作用函数执行之后再执行
            const p = Promise.resolve()
            p.then(job)
          } else {
            job()
          }
        }
      }
    )

    if (options?.immediate) {
      job()
    } else {
      oldValue = effectFn()
    }
  }


  // 测试

  // 定义obj为响应式数据
  const obj = reactive({ foo: 1, bar: 0 })


  const sumRes = computed(() => obj.foo + obj.bar)//注意sumRes不是响应式

  console.log('computed1', sumRes.value)
  console.log('computed2', sumRes.value)// 执行

  obj.foo++ // 执行执行 trigger(newCompute, 'value') 如果没有下面effect,其实和computed没关系

  console.log('computed3', sumRes.value)// 重新执行副作用函数,获取value,值改变

  // 这个时候修改sumRes,不会触发任何效果,修改没有用
  sumRes.value = 11

  console.log('computed4', sumRes.value)// 重新执行副作用函数,获取value,值改变
  effect(() => {
    console.log(sumRes.value)
  })

  obj.foo++ // 这个时候修改obj,sumRes才会执行副作用函数才有效


  // 异步测试函数
  let count = 0

  function fetch() {
    count++
    const res = count === 1 ? 'A' : 'B'
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(res)
      }, count === 1 ? 1000 : 100)
    })
  }

  let finallyData

  watch(() => obj.foo, async (newVal, oldVal, onInvalidate) => {
    let valid = true
    onInvalidate(() => {
      valid = false
    })
    const res = await fetch()

    if (!valid) return

    finallyData = res
    console.log(finallyData)
  })

  obj.foo++
  setTimeout(() => {
    obj.foo++
  }, 200)

  /// 脱ref
  const newObj = proxyRefs({ ...toRefs(obj) })


  console.log(newObj.foo)
  console.log(newObj.bar)

  newObj.foo = 100
  console.log(newObj.foo)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值