🔥 都2023了,还不能手写Vue3响应式原理?

696 阅读4分钟

帅照镇楼,哈哈哈 祝各位拿offer拿到手软

Vue3使用了ES6的Proxy来实现其底层的响应式系统,相比于Vue2的Object.defineProperty实现方式,具有更好的性能和扩展性。

reactive 函数

Vue3中,我们可以通过reactive函数将一个对象变成响应式对象。

import { reactive } from 'vue'

const state = reactive({
  count: 0,
})

上面的代码中,我们将一个包含count属性的对象传入reactive函数,返回的结果是一个响应式对象state。当我们修改state.count属性时,相关的组件会自动更新。

reactive函数的实现如下:

function reactive(target) {
  // 如果目标已经是一个Proxy对象了,则直接返回
  if (target && target.__v_isReadonly) {
    return target
  }

  // 创建一个响应式代理对象
  const observed = new Proxy(target, baseHandlers)
  
  // 将标记置为响应式对象
  def(target, '__v_isReactive', true)
  
  // 返回响应式对象
  return observed
}

reactive函数接收一个普通的对象作为参数,并返回一个响应式代理对象。在这个函数中,我们主要做了以下几件事情:

  1. 判断传入的对象是否已经是响应式对象,如果是,则直接返回
  2. 创建一个响应式代理对象,使用Proxy代理传入的对象target
  3. target添加一个__v_isReactive标志,表示这个对象已经变成了响应式对象
  4. 返回响应式代理对象

ref 函数

除了对象之外,Vue3还提供了一种基础类型数据的响应式实现方式,称为ref

import { ref } from 'vue'

const count = ref(0)

上面的代码中,我们通过ref函数将数字0变成了一个响应式的数据count

ref函数的实现如下:

function ref(value) {
  // 如果value本身就是一个ref对象了,则直接返回
  if (isRef(value)) {
    return value
  }

  // 创建一个包含value属性的响应式对象
  const obj = reactive({
    value
  })

  // 定义get/set方法
  const refImpl = {
    get value() {
      track(obj, 'value')
      return obj.value
    },
    set value(newVal) {
      if (newVal !== obj.value) {
        obj.value = newVal
        trigger(obj, 'value')
      }
    }
  }

  // 将标记置为ref对象
  def(obj, '__v_isRef', true)

  // 返回带有get/set方法的对象
  return refImpl
}

ref函数接收一个值作为参数,并返回一个带有getset方法的对象。在这个函数中,我们主要做了以下几件事情:

  1. 判断传入的值是否已经是一个ref对象,如果是,则直接返回
  2. 创建一个包含值属性的响应式对象
  3. 定义get方法和set方法,用于获取和设置值,并在值发生变化时触发更新
  4. 给响应式对象添加一个__v_isRef标志,表示这个对象已经变成了ref对象
  5. 返回带有getset方法的对象

computed 函数

除了基础类型数据和对象之外,Vue3还提供了一种计算属性的实现方式,称为computed

import { ref, computed } from 'vue'

const count = ref(0)
const doubleCount = computed(() => count.value * 2)

上面的代码中,我们使用computed函数创建了一个计算属性doubleCount,它的值是count.value的两倍。

computed函数的实现如下:

function computed(getterOrOptions) {
  let getter
  let setter

  // 如果传入的是一个函数,则将其视为getter函数
  if (isFunction(getterOrOptions)) {
    getter = getterOrOptions
    setter = NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set || NOOP
  }

  // 创建一个ref对象,用于缓存计算结果
  const result = ref()

  // 定义计算属性的get方法
  const computedImpl = computedGetter({
    getter,
    result,
    scope: currentScope,
    isReadonly: true,
  })

  // 定义计算属性的set方法
  computedImpl.effect = effect(computedImpl)

  // 返回计算属性对象
  return computedImpl
}

computed函数接收一个函数或者一个选项对象作为参数,并返回一个计算属性对象。在这个函数中,我们主要做了以下几件事情:

  1. 判断传入的参数类型,如果是函数,则将其视为getter函数;如果是选项对象,则从中取出gettersetter函数。
  2. 创建一个ref对象,用于缓存计算结果。
  3. 定义计算属性的get方法,通过computedGetter函数创建一个响应式的getter函数,并传入getter函数、缓存结果的ref对象、所属的作用域以及是否只读等参数。
  4. 定义计算属性的set方法,通过effect函数创建一个响应式的effect函数,并传入之前定义的computedGetter函数作为回调函数。
  5. 返回计算属性对象。

总结

Vue3底层的响应式系统是通过使用ES6中的Proxy代理对象来实现的,相比于Vue2的Object.defineProperty实现方式,具有更好的性能和扩展性。在Vue3中,我们可以通过reactive函数将一个对象变成响应式对象,通过ref函数将基础类型数据变成响应式数据,通过computed函数创建计算属性。这些函数都是基于Proxy实现的,从而实现了高效的响应式更新。

Vue3的响应式原理相比Vue2有较大改进,主要表现在Proxy代理的使用上,可以更加高效地监听数据变化,而且不需要像Vue2那样深度递归遍历对象属性,从而提高了性能。同时,Vue3还引入了Reactivity API,使我们能够更加灵活地控制响应式系统,并且支持了Composition API风格的组合式API,让我们能够更加方便地管理和组织组件代码。

最近做了一个gpt聊天小程序 白昼降临

学习交流群

加作者:aixuexidekkk