学习日记01--vue3响应式原理简易实现

95 阅读4分钟

前言:

记录一下学习过程中的心得,让自己理解的更加透彻,简简单单开始,浇给~~

原理描述:

众所周知,vue3的响应式原理是基于Proxy来实现的,这解决了vue2中通过Object.defineProperty()来实现的原理中不能劫持监听数组相关的操作,以及劫持嵌套对象时需要递归操作等问题。Proxy直接可以劫持整个对象,并返回一个新对象。并且可以监听到数组的相关操作,Proxy有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has等等,这是 Object.defineProperty 不具备的。废话不多说,直接上代码吧。

reactive

这里只做了一些简单的判断,baseHandler是传给Proxy所定义劫持数据的规则。

function reactive(obj) {
  if (Object.prototype.toString.call(obj) !== '[object Object]') {
    return obj
  }

  return new Proxy(obj, baseHandler)
}

baseHandler

Reflect是一个内置的对象,它提供了拦截JavaScript操作的方法。它不是一个函数对象,,因此不可构造。Reflect对象提供了一些静态方法来操作对象,例如Reflect.get、Reflect.set、Reflect.deleteProperty等。这些方法可以用于读取、修改或删除对象的属性,并返回相应的结果。

const baseHandler = {
  get(target, key) {
    const res = Reflect.get(target, key)
    console.log('get', key)

    // 收集依赖
    track(target, key)

    return isObject(res) ? reactive(res) : res
  },
  set(target, key, value) {
    const res = Reflect.set(target, key, value)
    console.log('set', key)

    trigger(target, key)

    return res
  },
  deleteProperty(target, key) {
    const res = Reflect.deleteProperty(target, key)
    console.log('deleteProperty', key)

    trigger(target, key)

    return res
  }
}

track-收集依赖函数

主要是通过WeakMap来存储所收集依赖的对象,再通过Set来存储该依赖对象所能触发的所有响应式函数。

// 创建一个存储响应函数的数组
const effectStack = []
// 创建一个数据结构可以保存依赖和响应函数之间映射关系
const targetMap = new WeakMap()
function track(target, key) {
  // 获取响应函数
  const effect = effectStack[effectStack.length - 1]

  if (effect) {
    // 获取一下target值,不存在就创建
    let depMap = targetMap.get(target)

    if (!depMap) {
      depMap = new Map()
      targetMap.set(target, depMap)
    }

    // 获取depMap对应的依赖集合,不存在则创建
    let deps = depMap.get(key)
    if (!deps) {
      deps = new Set()
      depMap.set(key, deps)
    }

    // 响应函数放入deps
    deps.add(effect)
  }
}

trigger-执行所有响应式函数

// 把依赖相关的响应函数集合拿出来全部执行一遍
function trigger(target, key) {
  // 从依赖关系中获取响应函数集合
  const depMap = targetMap.get(target)
  if (!depMap) {
    return
  }

  const deps = depMap.get(key)
  if (deps) {
    deps.forEach(dep => dep())
  }
}

effect函数

通过上述几个函数,数据响应式的劫持及触发功能已经基本实现,但是还是需要实现一个收集副作用(响应函数)的方法,其作用是在对某个值进行响应式处理后,可以监听这个值变化后处理一些事情。

function effect(fn) {
  // 获取响应函数
  const e = createReactiveEffect(fn)
  // 立刻执行它,触发依赖收集过程
  e()
  return e
}
function createReactiveEffect(fn) {
  const effect = function() {
    try {
      // 1.保存fn
      effectStack.push(effect)

      // 2.执行fn
      return fn()
    } finally {
      // 3.取消fn保存
      effectStack.pop()
    }
  }
  return effect
}

试验一下

//响应式处理
const state = reactive({ foo: 'foo', bar: { n: 1 } })
//收集每个响应数据的副作用函数
effect(() => {
  console.log('effect1', state.foo)
})
effect(() => {
  console.log('effect2', state.foo, state.bar.n)
})

// state.foo
// state.foo = 'fooooo'
// state.bar = 'bar'
// state.bar
// delete state.bar
state.bar.n = 10

完整代码

// vue3 Proxy  ref
const isObject = v => typeof v === 'object' && v !== null

const baseHandler = {
  get(target, key) {
    const res = Reflect.get(target, key)
    console.log('get', key)

    // 收集依赖
    track(target, key)

    return isObject(res) ? reactive(res) : res
  },
  set(target, key, value) {
    const res = Reflect.set(target, key, value)
    console.log('set', key)

    trigger(target, key)

    return res
  },
  deleteProperty(target, key) {
    const res = Reflect.deleteProperty(target, key)
    console.log('deleteProperty', key)

    trigger(target, key)

    return res
  }
}

function reactive(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj
  }

  return new Proxy(obj, baseHandler)
}

const effectStack = []
function effect(fn) {
  // 获取响应函数
  const e = createReactiveEffect(fn)
  // 立刻执行它,触发依赖收集过程
  e()
  return e
}
function createReactiveEffect(fn) {
  const effect = function() {
    try {
      // 1.保存fn
      effectStack.push(effect)

      // 2.执行fn
      return fn()
    } finally {
      // 3.取消fn保存
      effectStack.pop()
    }
  }
  return effect
}

// 创建一个数据结构可以保存依赖和响应函数之间映射关系
const targetMap = new WeakMap()
function track(target, key) {
  // 获取响应函数
  const effect = effectStack[effectStack.length - 1]

  if (effect) {
    // 获取一下target值,不存在就创建
    let depMap = targetMap.get(target)

    if (!depMap) {
      depMap = new Map()
      targetMap.set(target, depMap)
    }

    // 获取depMap对应的依赖集合,不存在则创建
    let deps = depMap.get(key)
    if (!deps) {
      deps = new Set()
      depMap.set(key, deps)
    }

    // 响应函数放入deps
    deps.add(effect)
  }
}

// 把依赖相关的响应函数集合拿出来全部执行一遍
function trigger(target, key) {
  // 从依赖关系中获取响应函数集合
  const depMap = targetMap.get(target)
  if (!depMap) {
    return
  }

  const deps = depMap.get(key)
  if (deps) {
    deps.forEach(dep => dep())
  }
}

const state = reactive({ foo: 'foo', bar: { n: 1 } })

effect(() => {
  console.log('effect1', state.foo)
})
effect(() => {
  console.log('effect2', state.foo, state.bar.n)
})

// state.foo
// state.foo = 'fooooo'
// state.bar = 'bar'
// state.bar
// delete state.bar
state.bar.n = 10