Vue3源码-reactive解析

294 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情

Vue3 Reactive源码解析

在Vue3中,响应式系统被完全重写,使用了一个新的API:reactive。本文将对其源代码进行解析。

什么是响应式?

在Vue中,响应式是指当数据发生变化时,界面会自动更新反映这些变化。这种机制是通过Vue的响应式系统实现的。

Vue3的响应式系统

在Vue3中,我们使用reactive函数来创建响应式对象。该函数接受一个普通对象作为参数,并将其转换为响应式对象。

import { reactive } from 'vue'

const state = reactive({
  count: 0
})

reactive函数的实现

我们来看一下reactive函数的实现。下面是它的简化版本:

function reactive(obj) {
  // ...
  return new Proxy(obj, {
    // ...
  })
}

reactive函数接受一个普通对象作为参数,并返回一个代理对象。代理对象会拦截对原始对象的访问,并在必要时触发更新。

Proxy

在Vue3的响应式系统中,Proxy扮演了一个非常重要的角色。它是ES6中的一个新特性,可以拦截对象的访问并做出响应。

下面是一个例子,演示了如何使用Proxy来拦截对象的访问:

const obj = {
  name: 'Alice',
  age: 20
}

const proxy = new Proxy(obj, {
  get(target, key, receiver) {
    console.log(`Getting ${key}`)
    return Reflect.get(target, key, receiver)
  },
  set(target, key, value, receiver) {
    console.log(`Setting ${key} to ${value}`)
    return Reflect.set(target, key, value, receiver)
  }
})

console.log(proxy.name) // Getting name, Alice
proxy.age = 21 // Setting age to 21

在上面的例子中,我们创建了一个代理对象proxy,它会拦截对原始对象obj的访问。当我们访问proxy的属性时,会触发get方法。当我们设置proxy的属性时,会触发set方法。

Reactive对象的代理

在Vue3中,我们通过reactive函数创建的对象是一个代理对象。这个代理对象会拦截对原始对象的访问,并在必要时触发更新。

下面是一个简化版的reactive函数:

function reactive(obj) {
  const observed = new Proxy(obj, {
    get(target, key, receiver) {
      // ...
    },
    set(target, key, value, receiver) {
      // ...
    }
  })
  return observed
}

reactive函数返回一个代理对象observed,它会拦截对原始对象obj的访问。这个代理对象有两个拦截器:getset

get拦截器

当我们访问代理对象的属性时,get拦截器会被触发。下面是一个简化版的get拦截器:

get(target, key, receiver) {
  const value = Reflect.get(target, key, receiver)
  track(target, 'get', key)
  return value
}

get拦截器会调用Reflect.get方法来获取原始对象上的值。然后,它会调用track函数来记录这个属性被访问了。

set拦截器

当我们设置代理对象的属性时,set拦截器会被触发。下面是一个简化版的set拦截器:

set(target, key, value, receiver) {
  const result = Reflect.set(target, key, value, receiver)
  trigger(target, 'set', key, value)
  return result
}

set拦截器会调用Reflect.set方法来设置原始对象上的值。然后,它会调用trigger函数来触发更新。

track函数

track函数用于记录属性的访问。它会将属性和依赖关系存储在一个全局的Map对象中。

const targetMap = new WeakMap()

function track(target, type, key) {
  // 获取当前的响应式对象
  const currentEffect = activeEffectStack[activeEffectStack.length - 1]

  if (currentEffect) {
    // 获取target对象的Map
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      // 如果Map不存在,则创建一个新的Map
      depsMap = new Map()
      targetMap.set(target, depsMap)
    }

    // 获取key对应的Set
    let dep = depsMap.get(key)
    if (!dep) {
      // 如果Set不存在,则创建一个新的Set
      dep = new Set()
      depsMap.set(key, dep)
    }

    // 将当前的effect添加到Set中
    dep.add(currentEffect)
  }
}

trigger函数

trigger函数用于触发更新。它会获取属性的依赖关系,并执行它们对应的函数。

function trigger(target, type, key, value) {
  // 获取target对象的Map
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // 如果Map不存在,则说明没有依赖关系需要更新
    return
  }

  // 获取key对应的Set
  const dep = depsMap.get(key)
  if (dep) {
    // 执行Set中所有effect的函数
    dep.forEach(effect => {
      effect()
    })
  }
}

总结

在Vue3中,响应式系统使用了ProxyMap等新特性来实现。通过reactive函数创建的对象是一个代理对象,它会拦截对原始对象的访问,并在必要时触发更新。track函数用于记录属性的依赖关系,trigger函数用于触发更新。

参考资料