深入理解Vue响应式原理-源码解析

vue的响应式原理是通过劫持对象的get/set操作,在get时记录记录一个依赖到Map中,在set时触发更新,从Map中拿出依赖执行,实现响应式。

使用到了一下数据结构,用于依赖收集

  • Map
  • WeakMap
  • Set

参考资料

  • 《Vue.js设计与实现》
  • Vue 3.5.19 源码

创建响应对象和数据劫持

创建响应对象

源码位置 packages/reactivity/src/reactive.ts

JavaScript 复制代码
export const reactiveMap = new WeakMap();

function createReactiveObject(target, baseHandlers, proxyMap) {
  if (typeof target !== 'object' || target === null) {
    return target;
  }
  const existingProxy = proxyMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }
  const proxy = new Proxy(target, baseHandlers);
  proxyMap.set(target, proxy);
  return proxy;
}

export function reactive(target) {
    // ...
    return createReactiveObject(
        target,
        false,
        mutableHandlers,
        mutableCollectionHandlers,
        reactiveMap,
      )
}

依赖订阅和更新

源码位置packages/reactivity/src/dep.ts

JavaScript 复制代码
const targetMap = new WeakMap();

function track(target, key) {
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Set();
    depsMap.set(key, dep);
  }
  dep.add(activeEffect);
}

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const dep = depsMap.get(key);
  if (dep) {
    dep.forEach(effect => effect());
  }
}

整体流程

  1. 创建响应式对象 :通过 reactive 函数将普通对象转换为响应式对象。
  2. 依赖收集:在读取对象属性时,收集依赖于该属性的副作用函数。
  3. 触发更新:在设置对象属性时,通知所有依赖于该属性的副作用函数执行。

为什么使用Proxy而不是defineProperty

vue2中使用Object.defineProperty来劫持数据,vue3使用Proxy来劫持数据

  • 对象属性监听能力
    • 只能监听已存在的属性,新增或删除属性需要手动使用Vue.setVue.delete
    • Proxy是直接代理整个对象,他可以自动监听属性的新增和删除
  • 数组监听
    • Object.defineProperty不能监听数组的变化,因此vue2需要重写数组方法来记录依赖
    • Proxy可以直接监听数组的所有操作
  • 性能问题
    • Object.defineProperty因为是对属性的代理,所以对象需要递归遍历,性能较弱
    • Proxy直接代理整个对象
  • 监听Map、Set数据结构
    • Object.defineProperty不能监听,Proxy可以监听

ref和reactive(普通类型的响应式处理)

reactive函数只能支持引用类型,所以vue3使用ref来处理普通类型,通过构建一个RefImpl对象,让普通类型也可以通过get set处理依赖

  • ref对于value是引用类型的值,会直接使用reactive处理
  • ref对于value是普通类型的值,会通过get value方法记录依赖,通过set value触发依赖

源码位置 packages/reactivity/src/ref.ts

TypeScript 复制代码
export function ref(value?: unknown) {
  return createRef(value, false)
}

function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

class RefImpl<T = any> {
  _value: T;
  private _rawValue: T;

  dep: Dep = new Dep();

  public readonly [ReactiveFlags.IS_REF] = true;

  constructor(value: T, isShallow: boolean) {
    this._rawValue = isShallow ? value : toRaw(value);
    this._value = isShallow ? value : toReactive(value);
  }

  get value() {
    this.dep.track();
    return this._value;
  }

  set value(newValue) {
    const oldValue = this._rawValue;
    if (hasChanged(newValue, oldValue)) {
      this._rawValue = newValue;
      this._value = newValue;
      this.dep.trigger();
    }
  }
}

reactive和shallowReactive

reactiveshallowReactive 函数,用于创建深度和浅度响应式对象

  • reactive会递归遍历对象的所有引用类型属性,让整个对象都有响应式
  • shallowReactive只会遍历对象的第一层的属性,只有第一层有响应式

refshallowRef同理

相关推荐
软件技术NINI1 天前
娃娃店html+css 4页
前端·css·html
VX:Fegn08951 天前
计算机毕业设计|基于springboot + vue乡村振兴服务系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
wordbaby1 天前
TanStack Router 路径参数(Path Params)速查表
前端
盟接之桥1 天前
盟接之桥--说制造:从“找缝隙”到“一万米深”——庖丁解牛式的制造业精进之道
大数据·前端·数据库·人工智能·物联网·制造
巴拉巴拉~~1 天前
Flutter 通用滑块组件 CommonSliderWidget:单值 / 范围 + 刻度 + 标签 + 样式自定义
开发语言·前端·javascript
期待のcode1 天前
验证码实现
java·vue.js
韭菜炒大葱1 天前
现代前端开发工程化:Vue3 + Vite 带你从 0 到 1 搭建 Vue3 项目🚀
前端·vue.js·vite
老华带你飞1 天前
志愿者服务管理|基于springboot 志愿者服务管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·spring
九歌AI大模型1 天前
白嫖完两个 Kiro 账号,我终于搞懂Spec驱动的AI编程范式了
ai编程·cursor·trae
栀秋6661 天前
面试常考的最长递增子序列(LIS),到底该怎么想、怎么写?
前端·javascript·算法