Vue.js 3.0 响应式系统的实现原理?

一、Vue.js 响应式回顾

1. Proxy 对象实现属性监听

2. 多层属性嵌套,在访问属性过程中处理下一级属性

3. 默认监听动态添加的属性

4. 默认监听属性的删除操作

5. 默认监听数组索引和 length 属性

6. 可以作为单独的模块使用

 

二、核心方法

1. reactive / ref / toRefs / computed

2. effect 

3. track

4. trigger

 

三、reactive -对象

1. 接收一个参数,判断这个参数是否是对象

2. 创建拦截器对象 handler,设置 get / set / deleteProperty

3. 返回 Proxy 对象

export function reactive(target) {
  const handler = {
    get(target, key, receiver) {
    },
    set(target, key, value, receiver) {
    },
    deleteProperty(target, key, receiver) {
    }
  }

  return new Proxy(target, handler) // targe 目标对象,handler 
}

 

四、ref - 基本数据类型

ref 仅仅适用于 基本数据类型的响应式

export function ref(raw) {
  // 判断 raw 是否是ref 创建的对象,如果是的话直接返回
  if (isObject(raw) && raw.__v_isRef) {
    return
  }
  let value = convert(raw) // 将 raw 转化为响应式对象,包括对象的对象
  const r = {
    __v_isRef: true, // ref 的标识
    get value() {
      return value
    },
    set value(newValue) {
      if (newValue !== value) {
        raw = newValue
        value = convert(raw)  // 将 newValue 转化为响应式对象,包括对象的对象
      }
    }
  }
  return r
}

  

五、toRefs - 引用数据类型 ( Array, Object )

toRefs 将 引用数据类型,遍历到 目标对象上,可实现解构方法,因 reactive 无法实现解构,可通过此方法进行解决

export function toRefs(proxy) {
  const ret = proxy instanceof Array ? new Array(proxy.length) : {}

  for (const key in proxy) {
    ret[key] = toProxyRef(proxy, key) // 将目标对象的值分别挂载到最外层,可实现属性-解构
  }

  return ret
}

function toProxyRef(proxy, key) {
  const r = {
    __v_isRef: true,
    get value() {
      return proxy[key]
    },
    set value(newValue) {
      proxy[key] = newValue
    }
  }
  return r
}

  

六、收集依赖

1. 剖析:

收集依赖的过程就是,分为三部分

- targetMap:目标对象,

- depsMap: 目标对象的属性名称,

- dep:收集的effect函数

 

注解: 查看下图,会更帮助你理解

targetMap: {

  目标对象:depsMap: {

    目标对象的属性名称:dep: [

      effect收集的函数

    ]

  }

}

 

 

 

2. effect - 访问响应式对象属性,去收集依赖

let activeEffect = null // effect收集的函数
export function effect (callback) {
  activeEffect = callback
  callback() // 访问响应式对象属性,去收集依赖
  activeEffect = null
}

  

3. track - 将收集的依赖添加到dep中

let targetMap = new WeakMap() // 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。(Why used weakMap? 👇)

export function track (target, key) {
  if (!activeEffect) return
  let depsMap = targetMap.get(target) // 获取 targetMap的键中是否存在 ( target = 目标对象 )
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map())) // 如否,(target = 目标对象) 做为键进行赋值 = new Map()
  }
  let dep = depsMap.get(key) // new Map() 中是否存在 ( key = 目标对象的属性 )?
  if (!dep) {
    depsMap.set(key, (dep = new Set())) // 如否,key的值 =  new Set() 
  }
  dep.add(activeEffect) // dep 内部 加入 依赖函数
}

  

4. trigger - 触发更新

export function trigger (target, key) {
  const depsMap = targetMap.get(target) // 获取 targetMap的键中是否存在 ( target = 目标对象 )
  if (!depsMap) return
  const dep = depsMap.get(key) // 获取 effect依赖的函数-数组
  if (dep) {
    dep.forEach(effect => {
      effect() // 触发effect依赖的函数
    })
  }
}

  

 

5. computed - 计算属性-触发-收集的依赖

export function computed (getter) {
  const result = ref() // 基础数据类型的响应式

  effect(() => (result.value = getter())) // getter 为计算属性中的方法

  return result
}

  

6. 重写- reactive 内的收集依赖和触发更新

const isObject = val => val !== null && typeof val === 'object' // 判断是否是对象
const convert = target => isObject(target) ? reactive(target) : target // 如对象的值同是对象,则需要将其设置为响应式
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) => hasOwnProperty.call(target, key) // 判断对象自身属性中是否具有指定的属性

export function reactive (target) {
  if (!isObject(target)) return target

  const handler = {
    // 读取目标对象
    get (target, key, receiver) {
      // 收集依赖
      track(target, key)
      const result = Reflect.get(target, key, receiver)
      return convert(result)
    },
    // 修改目标对象
    set (target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver)
      let result = true
      if (oldValue !== value) {
        result = Reflect.set(target, key, value, receiver)
        // 触发更新
        trigger(target, key)
      }
      return result
    },
    // 删除目标对象
    deleteProperty (target, key) {
      const hadKey = hasOwn(target, key)
      const result = Reflect.deleteProperty(target, key)
      if (hadKey && result) {
        // 触发更新
        trigger(target, key)
      }
      return result
    }
  }

  return new Proxy(target, handler)
}

 

7. 重写-ref 实现收集依赖和触发更新

export function ref(raw) {
  // 判断 raw 是否是ref 创建的对象,如果是的话直接返回
  if (isObject(raw) && raw.__v_isRef) {
    return
  }
  let value = convert(raw) // 将 raw 转化为响应式对象,包括对象的对象
  const r = {
    __v_isRef: true, // ref 的标识
    get value() {
      // 收集依赖
      track(r, 'value') // 用于 ref(*).value
      return value
    },
    set value(newValue) {
      if (newValue !== value) {
        raw = newValue
        value = convert(raw)  // 将 newValue 转化为响应式对象,包括对象的对象
        // 触发更新
        trigger(r, 'value')
      }
    }
  }
  return r
}

  

8. 总结:

看到这个地方的时候,我们就已经对收集依赖,有了一定的理解,当我们执行 computed 方法 时候,会将 computed 内的函数存储effect 函数内部,做为收集的函数,当我们访问 computed 内部 目标对象的时候,目标对象会进入到 reactive的 get方法 内部进行收集 computed 的函数作为 依赖, 当修改 目标对象 的时候,通过 目标对象的 set 方法,修改 目标对象的值,并触发 computed 的函数,此时 保证每个 目标对象 值修改后,可以及时的更新

 

 

七、reactive VS ref

1. ref 可以把基本数据类型数据,转化响应式对象

2. ref 返回的对象,重新赋值成对象是响应式的

3. reactive 返回的对象,重新赋值丢失响应式

4. reactive 返回的对象不可以解构

 

reactive

    const product = reactive({
      name: 'iPhone',
      price: 5000,
      count: 3
    })

  

ref

   const price = ref(999)

  

 

posted @ 2021-03-17 18:16  小短腿奔跑吧  阅读(271)  评论(0编辑  收藏  举报