vue3源码实现--reactive篇

42 阅读3分钟

基本功能

官网介绍

根据官方的文档,我们可以对 reactive 做出基本功能的以下总结

  • reactive() 将使对象本身具有响应性,返回一个Proxy
  • 只有代理对象是响应式的,更改原始对象不会触发更新
  • reactive() 将深层地转换对象,当访问嵌套对象时,它们也会被 reactive() 包装
  • 对同一个原始对象调用 reactive() 总是返回同样的代理对象

好接下来我们一起来实现以上四个基本功能

1. reactive() 将使对象本身具有响应性
  • 基本使用
const origin = { a:1 }
const observe = reactive(origin)

console.log(observe.a)  // 1
  • 代码实现

众所周知 vue2.x 的响应式原理的实现是通过 Object.defineProperty 对对象的 set get 方法进行响应式拦截;

而在 vue3.x 中的响应式是通过 Proxy 实现的

function reactive(origin) {
  const observe = new Proxy(origin, {
    get(target, key, recevier) {
      // TODO 依赖收集
      console.log('依赖收集', target, key ,recevier)
      const res = Reflect(target, key, recevier)

      return res
    },
    set(target, key, value, recevier) {
      // TODO 触发响应
      console.log('触发响应')
      Reflect.set(target, key, value, recevier)
    }
  })

  return observe
}


const originData = { a: 1 }
const observeData = reactive(originData)

console.log(observeData.a) // 1
console.log(observeData === originData) // false

但是当前的代码并不满足 第三条特点, 深层次转换

const  originData = { a: { b: 1 } }
const observeData = reactive(originData)

console.log(observeData.a.b) // 1 


以上代码 会触发get 但是 监听到的key 是 a 而不是 b

因为 此时的 b 并不是一个 响应式的对象

我们需要对它包装一层, 修改 get 方法

非常简单,只需要判断我们获取到的值是否是对象,如果是一个对象我们就包裹一层 reactive


get(target, key, recevier) {
  // TODO依赖收集
  console.log('依赖收集', target, key ,recevier)
  const res = Reflect.get(target, key, recevier)

  // 如果 res 是对象的话 就包裹一层
  if (res !== null && typeof res === 'object') {
    return reactive(res)
  }

  return res
}

依赖收集 { a: { b: 2 } } a { a: { b: 2 } }

依赖收集 { b: 2 } b { b: 2 }

这样就会触发 两次 get 方法 从而在获取到深层的时候得到收集相应的依赖

到现在我们就已经满足三个特点了。

最后一个算是 一个小小的优化,相当于做了一个缓存,如果是同一个对象被 reactive,将会被返回同一个 Proxy 对象

const reactiveMap = new Map()

function reactive(origin) {
  const cachedItem = reactiveMap.get(origin)
  if (cachedItem) {
    return cachedItem
  }
  const observe = new Proxy(origin, {
    get(target, key, recevier) {
      // TODO依赖收集
      console.log('依赖收集', target, key, recevier)
      const res = Reflect.get(target, key, recevier)

      // 如果 res 是对象的话 就包裹一层
      if (res !== null && typeof res === 'object') {
        return reactive(res)
      }

      return res
    },
    set(target, key, value, recevier) {
      // TODO触发响应
      Reflect.set(target, key, value, recevier)
    }
  })

  reactiveMap.set(origin, observe)
  return observe
}


const originData = { a: 1 }
const observeData = reactive(originData)
const observeData2 = reactive(originData)

console.log(observeData === observeData2) // true

扩展方法

最后我们将实现 reactive 相关的方法

isReactive 判断是否是 由Reactive创建的

这个方法只需要 在 reactive 里面做适配即可

function isReactive(data) {
    return !!data['_Is_Reactive']
}

get(target, key, recevier) {
  // TODO依赖收集
  console.log('依赖收集', target, key ,recevier)
  
  // !!!!重要
  if (key === '_Is_Reactive') {
    return true
  }
  
  const res = Reflect.get(target, key, recevier)

  // 如果 res 是对象的话 就包裹一层
  if (res !== null && typeof res === 'object') {
    return reactive(res)
  }

  return res
}

const originData = { a: { b: 1 } }
const observeData = reactive(originData)

console.log(isReactive(observeData)) // true
console.log(isReactive(observeData.a)) // true
console.log(isReactive(observeData.a.b)) // true

至于依赖收集,响应触发 将在之后有机会再实现