039_Vue 3 响应式编程全面解析

一、前端编程的进化之路:从 DOM 操作到响应式编程

1.1 前端开发的原始阶段:直接 DOM 操作时代

前端开发的根本任务是实现用户界面与用户交互,早期通过直接操作 DOM 树来完成,数据状态、DOM 操作、事件处理高度耦合,维护成本极高。

javascript

运行

// 1999年的典型前端代码
function updateUserList() {
    // 1. 手动发送请求获取数据
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
            var users = JSON.parse(xhr.responseText);
            
            // 2. 手动清空DOM
            var userList = document.getElementById('user-list');
            userList.innerHTML = '';
            
            // 3. 手动创建并插入DOM元素
            // 注意:此处存在闭包陷阱,循环变量i共享导致点击时获取错误的userId
            for(var i = 0; i < users.length; i++) {
                // 修正方案:使用let声明i,或通过立即执行函数绑定当前i值
                let index = i; 
                var li = document.createElement('li');
                li.textContent = users[index].name;
                
                // 4. 手动绑定事件
                li.onclick = function() {
                    var userId = users[index].id;
                    // 更多DOM操作...
                };
                
                userList.appendChild(li);
            }
        }
    };
    xhr.open('GET', '/api/users');
    xhr.send();
}

// 核心问题:开发者需要同时管理
// 1. 数据状态
// 2. DOM操作
// 3. 事件处理
// 全部混在一起,难以维护

1.2 渐进改善阶段:jQuery 时代

jQuery 简化了 DOM 操作并解决了跨浏览器兼容性问题,但未解决数据与 UI 分离、状态管理混乱的核心问题,大型应用仍难以维护。

javascript

运行

// jQuery代码
$('#add-user').click(function() {
    $.ajax({
        url: '/api/users',
        method: 'POST',
        data: { name: $('#name').val() },
        success: function(user) {
            // 手动创建DOM并绑定事件
            $('<li>')
                .text(user.name)
                .click(function() {
                    // 处理点击
                })
                .appendTo('#user-list');
        }
    });
});

// 改善:
// 1. 简化了DOM操作
// 2. 解决了浏览器兼容性问题

// 但根本问题未解决:
// 1. 数据和UI依旧没有绑定
// 2. 状态管理混乱
// 3. 大型应用难以维护

1.3 框架革命:数据驱动视图时代

核心理念是将关注点从 “如何操作 DOM” 转移到 “如何管理数据”,主流框架通过不同技术路线实现了声明式、组件化、数据驱动的核心特征。

框架

核心思路

实现方式

React

函数式响应式

虚拟 DOM + 单向数据流

Vue

声明式响应式

响应式系统 + 模板编译

Angular

基于类的响应式

TypeScript + 依赖注入

共同特征

  • 声明式:描述 UI 应该是什么样子,而非一步步构建的过程
  • 组件化:将 UI 拆分为独立、可复用的组件
  • 数据驱动:数据变化自动更新视图

1.4 现代前端:从 “操作 DOM” 到 “声明状态” 的转变

现代前端开发的核心是从命令式的 DOM 操作,转向声明式的状态定义,框架自动完成状态到视图的映射。

javascript

运行

// 过去:关注"如何做"(命令式)
function handleClick() {
    // 1. 获取按钮元素
    var btn = document.getElementById('myButton');
    // 2. 修改按钮文本
    btn.textContent = '已点击';
    // 3. 禁用按钮
    btn.disabled = true;
}

// 目前:关注"是什么"(声明式)
<template>
  <!-- 声明UI状态 -->
  <button 
    :disabled="isClicked"
    @click="handleClick"
  >
    {{ buttonText }}
  </button>
</template>

<script setup>
// 声明数据状态
const isClicked = ref(false);
const buttonText = ref('点击我');

// 声明交互逻辑
const handleClick = () => {
  isClicked.value = true;
  buttonText.value = '已点击';
};
</script>

// 根本转变:从"操作DOM"到"声明状态"
// Vue自动处理状态到视图的映射

1.5 Vue 的响应式解决方案

Vue 选择了渐进式、易上手、高性能的技术路线,其响应式系统具备渐进式采用、声明式渲染、自动更新、组件化架构的核心特征。

vue

<!-- Vue的完整解决方案 -->
<template>
  <!-- 声明式模板 -->
  <div class="user-management">
    <UserList :users="filteredUsers" />
    <UserForm @add-user="addUser" />
    <UserSearch @search="updateFilter" />
  </div>
</template>

<script setup>
// 组合式API:逻辑组织更灵活
import { ref, computed, onMounted } from 'vue'

// 响应式数据
const users = ref([])
const searchQuery = ref('')

// 计算属性(自动缓存)
const filteredUsers = computed(() => {
  return users.value.filter(user => 
    user.name.includes(searchQuery.value)
  )
})

// 方法(纯逻辑)
const addUser = (newUser) => {
  users.value.push(newUser)
}

const updateFilter = (query) => {
  searchQuery.value = query
}

// 生命周期钩子
// 模拟接口请求:实际项目中需替换为真实API调用
const fetchUsers = async () => {
  // 模拟异步请求
  return new Promise(resolve => {
    setTimeout(() => resolve([{ name: '张三' }, { name: '李四' }]), 500)
  })
}

onMounted(async () => {
  users.value = await fetchUsers()
})
</script>

<style scoped>
/* 组件作用域样式 */
.user-management {
  padding: 20px;
}
</style>

二、观察者模式在 Vue 响应式系统中的实现

2.1 观察者模式的本质与定义

观察者模式(Observer Pattern)是一种行为型设计模式,定义了一对多的依赖关系,让多个观察者对象监听某一主题对象,当主题状态变化时,所有观察者自动收到通知并更新。

角色

职责

类比现实世界

Subject(主题)

维护观察者列表,提供添加 / 删除观察者的方法,状态变化时通知所有观察者

新闻报社

Observer(观察者)

定义更新接口,收到主题通知时执行更新操作

订阅报纸的读者

ConcreteSubject(具体主题)

具体主题实现,存储状态,状态改变时通知观察者

《人民日报》报社

ConcreteObserver(具体观察者)

具体观察者实现,维护对具体主题的引用

具体的订阅者(张三、李四)

javascript

运行

// 经典观察者模式实现

// 主题接口
class Subject {
  constructor() {
    this.observers = []; // 观察者列表
  }
  
  // 添加观察者
  attach(observer) {
    this.observers.push(observer);
  }
  
  // 移除观察者
  detach(observer) {
    const index = this.observers.indexOf(observer);
    if (index > -1) {
      this.observers.splice(index, 1);
    }
  }
  
  // 通知所有观察者
  notify() {
    for (const observer of this.observers) {
      observer.update(this);
    }
  }
}

// 观察者接口
class Observer {
  update(subject) {
    // 观察者收到通知后的更新逻辑
  }
}

// 具体主题:温度传感器
class TemperatureSensor extends Subject {
  constructor() {
    super();
    this.temperature = 20; // 当前温度
  }
  
  // 温度变化
  setTemperature(newTemp) {
    if (this.temperature !== newTemp) {
      this.temperature = newTemp;
      this.notify(); // 温度变化,通知所有观察者
    }
  }
}

// 具体观察者:温度显示器
class TemperatureDisplay extends Observer {
  constructor(name) {
    super();
    this.name = name;
  }
  
  update(subject) {
    console.log(`${this.name} 显示温度: ${subject.temperature}°C`);
  }
}

// 使用
const sensor = new TemperatureSensor();
const display1 = new TemperatureDisplay('客厅显示器');
const display2 = new TemperatureDisplay('卧室显示器');

sensor.attach(display1);
sensor.attach(display2);

sensor.setTemperature(25);
// 输出:
// 客厅显示器 显示温度: 25°C
// 卧室显示器 显示温度: 25°C

2.2 Vue 响应式系统中的观察者模式实现

Vue 的响应式系统是观察者模式的高级自动化实现,在经典模式基础上实现了自动依赖收集、细粒度依赖追踪等核心改善,其核心角色对应关系如下:

观察者模式角色

Vue 中的对应实现

具体说明

Subject(主题)

响应式数据(reactive 对象 /ref 值)

被观察的数据状态

Observer(观察者)

副作用函数(渲染函数 /watchEffect/computed)

依赖于响应式数据的函数

ConcreteSubject

Proxy 包装的响应式对象

具体的数据对象,如 reactive ({count: 0})

ConcreteObserver

具体的组件渲染函数、计算属性函数

如 () => console.log (count.value)

attach()

依赖收集(track)

副作用函数执行时自动建立依赖关系

detach()

依赖清理

组件卸载或副作用停止时清理依赖

notify()

触发更新(trigger)

数据变化时自动通知所有依赖的副作用函数

2.2.1 自动依赖收集

传统观察者模式需手动注册观察者,而 Vue 可自动识别副作用函数依赖的响应式数据并建立关联:

javascript

运行

// 传统观察者模式:需要手动注册观察者
sensor.attach(display1);  // 手动调用attach

// Vue响应式系统:自动收集依赖
const count = ref(0);

// 当这个函数执行时,Vue自动记录:
// "这个函数依赖于count.value"
watchEffect(() => {
  console.log(`Count: ${count.value}`); // 自动建立依赖关系
});

2.2.2 细粒度依赖追踪

Vue 可追踪到对象的具体属性级别,不同副作用函数仅依赖自身使用的属性,数据变化时仅触发关联的副作用函数:

javascript

运行

// Vue可以追踪到对象的具体属性级别
const user = reactive({
  name: '张三',
  age: 25,
  address: {
    city: '北京',
    street: '长安街'
  }
});

// 不同的副作用可以依赖不同的属性
watchEffect(() => {
  console.log(`用户名: ${user.name}`); // 只依赖user.name
});

watchEffect(() => {
  console.log(`用户年龄: ${user.age}`); // 只依赖user.age
});

// 当user.name变化时,只触发第一个watchEffect
// 当user.age变化时,只触发第二个watchEffect

2.3 核心数据结构:全局依赖映射表

Vue 响应式系统的核心是三层数据结构(WeakMap<target, Map<key, Dep>>),用于准确记录 “哪个对象的哪个属性被哪些函数依赖”,是实现精准更新、高效内存管理的基础。

text

WeakMap<target, Map<key, Dep>>
├── target: 响应式对象
│   └── Map<key, Dep>
│       ├── key: 属性名
│       │   └── Dep: 依赖集合(Dependency)
│       │       ├── 副作用函数1
│       │       ├── 副作用函数2
│       │       └── ...
│       └── ...
└── ...

2.3.1 第一层:WeakMap<target, depsMap>

javascript

运行

// 第一层:WeakMap,键是响应式对象
const targetMap = new WeakMap();

// 为什么用WeakMap?
// 1. 不会阻止垃圾回收(当target不再被引用时,对应的depsMap会自动清理)
// 2. 避免内存泄漏
// 3. 键只能是对象,符合响应式数据的类型特征

2.3.2 第二层:Map<key, dep>

javascript

运行

// 第二层:Map,键是属性名,值是Dep(Dependency)实例
const depsMap = new Map();

// 示例:
// depsMap.set('name', nameDep);  // 'name'属性的依赖
// depsMap.set('age', ageDep);    // 'age'属性的依赖

2.3.3 第三层:Dep(Dependency,依赖集合)

javascript

运行

// 第三层:Dep,一般用Set实现,存储依赖某个属性的所有副作用函数
class Dep {
  constructor() {
    this.subscribers = new Set(); // 存储副作用函数的集合
  }
  
  // 收集依赖:将当前活跃的副作用函数加入订阅者列表
  depend() {
    if (activeEffect) { // activeEffect为当前执行的副作用函数
      this.subscribers.add(activeEffect);
    }
  }
  
  // 触发更新:执行所有依赖当前属性的副作用函数
  notify() {
    this.subscribers.forEach(effect => {
      if (effect !== activeEffect) {  // 避免循环依赖导致的重复执行
        effect();
      }
    });
  }
}

2.4 依赖收集流程详解

javascript

运行

// 1. 创建响应式数据
const user = reactive({ name: '张三', age: 25 });

// 2. 创建副作用函数
watchEffect(() => {
  console.log(`姓名: ${user.name}, 年龄: ${user.age}`);
});

// 3. Vue内部执行过程:
// a) 执行副作用函数,将activeEffect设为当前函数
// b) 读取 user.name → 触发Proxy的get拦截器
//    - 从targetMap中获取user对应的depsMap(不存在则创建)
//    - 从depsMap中获取'name'对应的Dep实例(不存在则创建)
//    - 调用Dep.depend() → 将activeEffect加入订阅者列表
// c) 读取 user.age → 重复上述过程,收集age属性的依赖

// 最终数据结构:
// targetMap = WeakMap {
//   user对象 → Map {
//     'name' → Set [ 副作用函数 ],
//     'age'  → Set [ 副作用函数 ]
//   }
// }

2.5 触发更新的精准性

javascript

运行

// 修改 user.name
user.name = '李四';

// Vue内部执行过程:
// a) 触发Proxy的set拦截器
// b) 从targetMap中获取user对应的depsMap
// c) 从depsMap中获取'name'对应的Dep实例
// d) 调用Dep.notify() → 执行所有依赖name属性的副作用函数

// 注意:不会触发依赖 user.age 的其他函数
// 这就是精准更新的关键!

2.6 嵌套对象的处理

从全局依赖映射表的视角,Vue 对嵌套对象的响应式处理本质是为每层对象单独构建依赖映射表,而非在父对象中直接监控嵌套属性:

javascript

运行

const company = reactive({
  name: 'Tech Corp',
  CEO: {
    name: 'Alice',
    age: 40
  }
});

watchEffect(() => {
  console.log(`CEO姓名: ${company.CEO.name}`);
});

// Vue内部执行逻辑:
// 1. 读取company.CEO → 触发company对象'CEO'属性的get拦截器,收集依赖
// 2. 对CEO对象进行递归代理,为其创建独立的WeakMap条目
// 3. 读取company.CEO.name → 触发CEO对象'name'属性的get拦截器,收集依赖
// 最终依赖关系记录在两层映射表中:
// - company对象的'CEO'属性对应的Dep实例
// - company.CEO对象的'name'属性对应的Dep实例

三、Vue 3 响应式原理:全局依赖映射表

3.1 核心数据结构:三层依赖映射表

Vue 响应式系统的核心是三层数据结构(WeakMap<target, Map<key, Dep>>),用于准确记录”哪个对象的哪个属性被哪些函数依赖”。

039_Vue 3 响应式编程全面解析

3.1.1 第一层:WeakMap<target, depsMap>

javascript

// 第一层:WeakMap,键是响应式对象
const targetMap = new WeakMap();

// WeakMap 的优势:
// 1. 不会阻止垃圾回收(当target不再被引用时,对应的depsMap会自动清理)
// 2. 避免内存泄漏
// 3. 键只能是对象,符合响应式数据的类型特征

3.1.2 第二层:Map<key, dep>

javascript

// 第二层:Map,键是属性名,值是Dep实例
const depsMap = new Map();

// 示例结构:
// depsMap = Map {
//   'name' → nameDep,
//   'age'  → ageDep,
//   'address' → addressDep
// }

3.1.3 第三层:Dep(依赖集合)

javascript

class Dep {
  constructor() {
    this.subscribers = new Set(); // 存储副作用函数的集合
  }
  
  // 收集依赖:将当前活跃的副作用函数加入订阅者列表
  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }
  
  // 触发更新:执行所有依赖当前属性的副作用函数
  notify() {
    this.subscribers.forEach(effect => {
      if (effect !== activeEffect) {  // 避免循环依赖导致的重复执行
        effect();
      }
    });
  }
}

3.2 依赖收集流程详解

javascript

// 完整示例:展示依赖收集全过程
const user = reactive({ name: '张三', age: 25 });

// 创建副作用函数
watchEffect(() => {
  console.log(`用户信息: ${user.name}, ${user.age}`);
});

// Vue内部执行过程:
// 1. 执行副作用函数,设置 activeEffect = 当前函数
// 2. 读取 user.name → 触发 get 拦截器
//    - 从 targetMap 获取 user 对应的 depsMap
//    - 从 depsMap 获取 'name' 对应的 Dep
//    - 调用 dep.depend() 收集依赖
// 3. 读取 user.age → 重复上述过程
// 4. 函数执行结束,清空 activeEffect

// 最终数据结构:
// targetMap = {
//   [user对象]: Map {
//     'name': Dep { subscribers: [副作用函数] },
//     'age':  Dep { subscribers: [副作用函数] }
//   }
// }

3.3 触发更新的精准性

javascript

// 修改数据时的精准更新
user.name = '李四';

// 触发流程:
// 1. 触发 set 拦截器
// 2. 从 targetMap 获取 user 对应的 depsMap
// 3. 从 depsMap 获取 'name' 对应的 Dep
// 4. 调用 dep.notify() → 仅执行依赖 name 的副作用函数

// ⚠️ 注意:不会触发依赖 user.age 的函数
// 这就是 Vue 响应式系统高性能的关键!

3.4 嵌套对象的处理

javascript

const company = reactive({
  name: 'Tech Corp',
  CEO: {
    name: 'Alice',
    age: 40
  }
});

watchEffect(() => {
  console.log(`CEO姓名: ${company.CEO.name}`);
});

// 嵌套对象的依赖收集:
// 1. 读取 company.CEO → 为 company 对象收集 'CEO' 属性的依赖
// 2. 读取 company.CEO.name → 为 CEO 对象收集 'name' 属性的依赖
// 3. 最终形成两层依赖映射表:
//    - company 对象: 'CEO' → Dep
//    - CEO 对象: 'name' → Dep

四、Vue 3 响应式 API 详解

4.1 创建响应式数据

4.1.1 ref – 支持基本类型与对象/数组

javascript

import { ref } from 'vue';

// 基本类型
const count = ref(0);
console.log(count.value);  // 读取值
count.value = 1;           // 修改值

// 对象类型(内部自动用 reactive 包装)
const user = ref({ name: '张三' });
user.value.name = '李四';

// 模板中的自动解包
<template>
  <div>{{ count }}</div>   <!-- 不需要 .value -->
  <div>{{ user.name }}</div> <!-- 嵌套属性也无需 .value -->
</template>

4.1.2 reactive – 用于对象(递归代理)

javascript

import { reactive } from 'vue';

const user = reactive({
  name: '张三',
  age: 25,
  address: {
    city: '北京' // 嵌套对象也会被递归代理
  }
});

// 直接修改属性
user.name = '李四';
user.address.city = '上海';

// ⚠️ 注意:reactive 返回的是 Proxy 对象,不是原始对象
console.log(user instanceof Proxy); // true

4.1.3 toRef/toRefs – 保留解构后的响应式

javascript

import { reactive, toRef, toRefs } from 'vue';

const user = reactive({ name: '张三', age: 25 });

// 单个属性转为 ref
const nameRef = toRef(user, 'name');
nameRef.value = '李四'; // 会更新原 user.name

// 所有属性转为 ref(常用于组合式函数返回值)
const { name, age } = toRefs(user);

// ⚠️ 注意:toRefs 创建的 ref 与原始属性共享同一个 Dep 实例
// 修改 age 会触发 user.age 对应的所有依赖函数
age.value = 26;

4.2 计算属性 computed

javascript

import { ref, computed } from 'vue';

const price = ref(100);
const quantity = ref(2);

// 只读计算属性(具有缓存机制)
const total = computed(() => price.value * quantity.value);

// 可写计算属性
const firstName = ref('张');
const lastName = ref('三');
const fullName = computed({
  get() {
    return `${firstName.value} ${lastName.value}`;
  },
  set(newName) {
    const [first, last] = newName.split(' ');
    firstName.value = first;
    lastName.value = last || '';
  }
});

// ⚠️ 注意:计算属性应该保持纯函数特性
// ❌ 错误:在计算属性中执行副作用
const badComputed = computed(() => {
  console.log('计算中...'); // 不应该有副作用
  return price.value * quantity.value;
});

4.3 侦听器:watch vs watchEffect

4.3.1 watch – 显式指定依赖

javascript

import { ref, reactive, watch } from 'vue';

const count = ref(0);
const user = reactive({ name: '张三' });

// 监听单个源
watch(count, (newVal, oldVal) => {
  console.log(`从 ${oldVal} 变为 ${newVal}`);
});

// 监听多个源
watch([count, () => user.name], ([newCount, newName], [oldCount, oldName]) => {
  console.log(`count: ${newCount}, name: ${newName}`);
});

// 深度监听(遍历对象所有属性,收集每个属性的依赖)
watch(user, (newUser) => {
  console.log('user 发生变化:', newUser);
}, { deep: true });

// 立即执行
watch(count, (newVal) => {
  console.log('当前值:', newVal);
}, { immediate: true });

4.3.2 watchEffect – 自动追踪依赖

javascript

import { ref, reactive, watchEffect } from 'vue';

const count = ref(0);
const user = reactive({ name: '张三' });

// 自动追踪依赖
watchEffect(() => {
  console.log(`count: ${count.value}, name: ${user.name}`);
  // Vue 会自动收集 count.value 和 user.name 的依赖
});

// 带清理函数的版本
watchEffect((onCleanup) => {
  const timer = setInterval(() => {
    console.log('定时执行');
  }, 1000);
  
  onCleanup(() => {
    clearInterval(timer); // 清理资源
  });
});

// ⚠️ 注意:watchEffect 默认立即执行
// 如果不需要立即执行,可以使用 { flush: 'post' } 选项

4.3.3 对比指南

场景

推荐使用

缘由

循环依赖风险

需要新旧值对比

watch

提供新旧值参数

依赖关系简单固定

均可

根据偏好选择

低-中

依赖关系动态变化

watchEffect

自动追踪依赖

中高

需要立即执行

watchEffect

默认立即执行

中高

需要懒执行

watch

默认懒执行

清理副作用

watchEffect

提供 onCleanup

中高

五、副作用函数与依赖追踪的常见陷阱

5.1 异步操作中的依赖丢失

javascript

// ❌ 问题:异步回调中的依赖无法被正确收集
const data = ref(null);

watchEffect(async () => {
  // 只有同步代码中的依赖会被收集
  const id = 1;
  const response = await fetch(`/api/data/${id}`);
  data.value = await response.json(); // 这行不会触发依赖重新收集
});

// ✅ 解决方案:将响应式数据放在同步代码中
watchEffect(() => {
  const id = 1;
  // 在同步代码中访问响应式数据
  const currentData = data.value;
  
  fetch(`/api/data/${id}`)
    .then(response => response.json())
    .then(json => {
      data.value = json; // 修改响应式数据
    });
});

5.2 条件判断中的依赖分支

javascript

const showDetails = ref(false);
const user = reactive({ name: '张三', details: {} });

// ❌ 问题:条件分支导致依赖收集不全
watchEffect(() => {
  console.log(`用户: ${user.name}`);
  if (showDetails.value) {
    console.log(`详情: ${user.details.age}`); // 初始时不会收集此依赖
  }
});

// 当 showDetails 变为 true 时,不会重新执行函数
// 因此 user.details.age 的变化不会被检测到

// ✅ 解决方案:使用 watch 或确保所有分支都被执行
watch(
  () => showDetails.value ? { name: user.name, details: user.details } : { name: user.name },
  (newVal) => {
    console.log('用户信息:', newVal);
  }
);

5.3 副作用函数中的 DOM 操作

javascript

import { ref, watchEffect, nextTick } from 'vue';

const count = ref(0);

// ❌ 问题:在副作用中直接操作 DOM
watchEffect(() => {
  if (count.value > 5) {
    // 直接操作 DOM,违背 Vue 的设计原则
    document.getElementById('message').style.color = 'red';
  }
});

// ✅ 解决方案:使用响应式数据驱动视图
const messageColor = ref('black');
watchEffect(() => {
  if (count.value > 5) {
    messageColor.value = 'red';
  }
});

// 或者在模板中使用计算属性
<template>
  <div :style="{ color: messageColor }">消息内容</div>
</template>

// ✅ 如果必须操作 DOM,使用 nextTick
watchEffect(() => {
  if (count.value > 5) {
    nextTick(() => {
      // 确保 DOM 已更新
      const element = document.getElementById('message');
      if (element) {
        element.style.color = 'red';
      }
    });
  }
});

六、循环依赖:场景分类与高危风险应对

6.1 循环依赖的场景分类

场景类型

触发链路

涉及 Dep 实例数

风险程度

排查难度

核心应对策略

直观自循环

单函数→单Dep→单函数

1

增加终止条件

隐蔽相互依赖

函数A→DepB→函数B→DepA

2

单向数据流

复杂网状依赖

函数A→DepB→函数B→DepC→函数C→DepA

≥3

状态分层

6.2 不同循环依赖场景示例

6.2.1 直观自循环(低风险)

javascript

const count = ref(0);

// 自循环:副作用函数修改自身依赖的数据
watchEffect(() => {
  console.log(`Count: ${count.value}`);
  
  // ⚠️ 危险:没有终止条件的自循环
  // count.value = count.value + 1;
  
  // ✅ 安全:添加终止条件
  if (count.value < 10) {
    count.value = count.value + 1;
  }
});

// Vue 的防护:异步更新队列防止栈溢出

6.2.2 隐蔽相互依赖(中风险)

javascript

// ❌ 危险:两个副作用函数相互依赖
const a = ref(0);
const b = ref(0);

watchEffect(() => {
  // 依赖 a,修改 b
  b.value = a.value + 1;
  console.log(`A: ${a.value}, B: ${b.value}`);
});

watchEffect(() => {
  // 依赖 b,修改 a
  a.value = b.value * 2;
  console.log(`B: ${b.value}, A: ${a.value}`);
});

// 触发链:A → B → A → B → ... 无限循环

6.2.3 复杂网状依赖(高风险)

javascript

// ❌ 危险:多个函数形成网状依赖
const data = reactive({ x: 1, y: 2, z: 3 });

watchEffect(() => { 
  data.y = data.x * 2; // 依赖 x,修改 y
  console.log(`x=${data.x}, y=${data.y}`);
});

watchEffect(() => { 
  data.z = data.y + 1; // 依赖 y,修改 z
  console.log(`y=${data.y}, z=${data.z}`);
});

watchEffect(() => { 
  data.x = data.z / 2; // 依赖 z,修改 x
  console.log(`z=${data.z}, x=${data.x}`);
});

// 触发链:x → y → z → x → ... 网状循环

6.3 循环依赖的防护策略

6.3.1 自循环防护:添加明确终止条件

javascript

const temperature = ref(20);

watchEffect(() => {
  // 明确的业务逻辑终止条件
  if (temperature.value < 25) {
    // 模拟加热过程
    setTimeout(() => {
      temperature.value += 1;
    }, 1000);
  } else {
    console.log('已达到目标温度');
  }
});

// 使用标志位防止重复执行
let isUpdating = false;
watchEffect(() => {
  if (isUpdating) return;
  
  isUpdating = true;
  try {
    // 可能引发循环的逻辑
    if (someCondition.value) {
      otherValue.value = calculate(newValue);
    }
  } finally {
    isUpdating = false;
  }
});

6.3.2 相互依赖防护:单向数据流 + computed

javascript

// ❌ 危险:相互依赖
const a = ref(0);
const b = ref(0);
watchEffect(() => b.value = a.value + 1);
watchEffect(() => a.value = b.value * 2);

// ✅ 安全:单向数据流
const a = ref(0);
// 使用 computed 创建只读的衍生数据
const b = computed(() => a.value + 1);

// 如需从 b 推导 a,使用独立函数
const updateAFromB = (newB) => {
  a.value = newB * 2;
};

// 调试:查看依赖关系
function logDependencies(target, key) {
  const depsMap = targetMap.get(target);
  const dep = depsMap?.get(key);
  if (dep) {
    console.log(`${key} 的依赖函数数量: ${dep.subscribers.size}`);
  }
}

6.3.3 网状依赖防护:状态分层 + 依赖图梳理

javascript

// ❌ 危险:网状依赖
const data = reactive({ x: 1, y: 2, z: 3 });
watchEffect(() => { data.y = data.x * 2; });
watchEffect(() => { data.z = data.y + 1; });
watchEffect(() => { data.x = data.z / 2; });

// ✅ 安全:状态分层设计
// 第一层:源头数据(唯一可变)
const source = ref(1);

// 第二层:衍生数据(computed,只读)
const derived1 = computed(() => source.value * 2);
const derived2 = computed(() => derived1.value + 1);

// 第三层:展示数据(可进一步计算)
const display = computed(() => `结果: ${derived2.value}`);

// 修改只能通过源头数据
const updateSource = (newValue) => {
  source.value = newValue;
};

6.4 调试与排查工具

javascript

// 1. 执行计数器
let executionCount = 0;
const MAX_EXECUTIONS = 50;

watchEffect(() => {
  executionCount++;
  console.log(`[调试] 第 ${executionCount} 次执行`);
  
  if (executionCount > MAX_EXECUTIONS) {
    console.warn('⚠️ 可能陷入循环依赖');
    debugger; // 自动断点
  }
  
  // 业务逻辑...
});

// 2. Vue DevTools 使用技巧
// - 打开 "Components" 标签查看组件树
// - 使用 "Timeline" 记录状态变化
// - 查看组件渲染次数和缘由

// 3. 最小化重现法
// 步骤1: 注释所有 watchEffect
// 步骤2: 逐个撤销注释,观察控制台
// 步骤3: 定位引发循环的特定函数

七、组件与响应式系统

7.1 组件的响应式作用域

vue

<!-- Counter.vue -->
<template>
  <div>{{ count }}</div>
</template>

<script setup>
import { ref, watchEffect, onUnmounted } from 'vue';

// 每个组件实例有自己的响应式作用域
const count = ref(0);

const stop = watchEffect(() => {
  console.log(`实例 count: ${count.value}`);
});

// 组件卸载时清理副作用
onUnmounted(() => {
  stop();
});
</script>

<!-- 使用多个实例 -->
<template>
  <Counter />  <!-- 实例1:独立作用域 -->
  <Counter />  <!-- 实例2:独立作用域 -->
</template>

7.2 组件间的数据共享

7.2.1 Props 传递响应式数据

vue

<!-- Parent.vue -->
<template>
  <Child :user="user" @update-name="updateUserName" />
  <p>父组件: {{ user.name }}</p>
</template>

<script setup>
import { reactive } from 'vue';
import Child from './Child.vue';

// 父组件拥有响应式数据
const user = reactive({ name: '张三' });

const updateUserName = (newName) => {
  user.name = newName; // 只有父组件能修改数据
};
</script>

<!-- Child.vue -->
<template>
  <div>子组件: {{ user.name }}</div>
  <button @click="handleClick">修改姓名</button>
</template>

<script setup>
const props = defineProps(['user']);
const emit = defineEmits(['update-name']);

const handleClick = () => {
  // ❌ 错误:直接修改 props
  // props.user.name = '李四';
  
  // ✅ 正确:通过事件通知父组件
  emit('update-name', '李四');
};
</script>

7.2.2 Provide/Inject 跨层级共享

vue

<!-- Ancestor.vue -->
<script setup>
import { provide, reactive } from 'vue';

// 提供响应式数据
const appState = reactive({
  theme: 'dark',
  language: 'zh-CN',
  user: { name: '张三' }
});

// 提供修改函数,保持单向数据流
const updateTheme = (newTheme) => {
  appState.theme = newTheme;
};

provide('appState', {
  state: appState,
  updateTheme
});
</script>

<!-- Descendant.vue -->
<script setup>
import { inject } from 'vue';

// 注入数据和方法
const { state, updateTheme } = inject('appState');

// 修改数据通过提供的方法
const toggleTheme = () => {
  updateTheme(state.theme === 'dark' ? 'light' : 'dark');
};
</script>

八、性能优化与最佳实践

8.1 响应式深度控制

8.1.1 shallowRef 和 shallowReactive

javascript

import { shallowRef, shallowReactive } from 'vue';

// 场景:大对象,仅顶层变化需要响应
const largeData = shallowRef({
  items: Array(10000).fill({ id: 1, data: '...' })
});

// 手动触发更新
const updateItem = (index, newItem) => {
  const newData = { ...largeData.value };
  newData.items[index] = newItem;
  largeData.value = newData; // 替换整个对象触发更新
};

// shallowReactive:仅第一层属性响应式
const shallowState = shallowReactive({
  nested: { count: 0 } // nested.count 变化不会触发更新
});

// ⚠️ 性能对比:在10000个属性的对象上
// reactive: 创建约10000个Dep实例
// shallowReactive: 创建1个Dep实例

8.1.2 markRaw 跳过响应式

javascript

import { markRaw, reactive } from 'vue';

// 常量数据,无需响应式
const constants = markRaw({
  PI: 3.14159,
  CONFIG: {
    version: '1.0.0',
    features: ['a', 'b', 'c']
  }
});

const state = reactive({
  // 标记为原始对象,不会创建依赖映射表
  config: constants,
  
  // 响应式数据
  user: { name: '张三' }
});

// ⚠️ 注意:markRaw 是永久性的
// 无法将 markRaw 的对象重新变为响应式

8.2 合理使用计算属性缓存

javascript

import { ref, computed } from 'vue';

const list = ref([
  { id: 1, name: 'Alice', active: true },
  { id: 2, name: 'Bob', active: false },
  // ... 更多数据
]);

// ✅ 使用 computed 缓存过滤结果
const activeUsers = computed(() => {
  console.log('计算 activeUsers'); // 仅依赖变化时执行
  return list.value.filter(user => user.active);
});

const inactiveUsers = computed(() => {
  console.log('计算 inactiveUsers');
  return list.value.filter(user => !user.active);
});

// 多次访问,只计算一次
console.log(activeUsers.value); // 计算一次
console.log(activeUsers.value); // 使用缓存

// ❌ 错误:在方法中重复计算
const getActiveUsers = () => {
  console.log('计算 getActiveUsers'); // 每次调用都执行
  return list.value.filter(user => user.active);
};

8.3 数组更新优化

javascript

const list = reactive([1, 2, 3]);

// ❌ 无效:直接通过索引修改
list[0] = 10; // 不会触发更新

// ✅ 有效:使用数组方法
list.splice(0, 1, 10); // 触发更新

// ✅ 有效:替换整个数组
list.value = [...list.value, 4];

// ✅ 性能优化:批量更新
const items = ref([]);

// 差:多次触发更新
const addItemsBad = (newItems) => {
  newItems.forEach(item => {
    items.value.push(item); // 每次 push 都触发更新
  });
};

// 好:单次触发更新
const addItemsGood = (newItems) => {
  items.value = items.value.concat(newItems); // 只触发一次更新
};

8.4 内存管理最佳实践

javascript

import { ref, watchEffect, onUnmounted } from 'vue';

// 1. 及时清理副作用
setup() {
  const data = ref(null);
  
  const stop = watchEffect(() => {
    // 副作用逻辑
  });
  
  onUnmounted(() => {
    stop(); // 清理副作用
  });
  
  return { data };
}

// 2. 避免循环引用
const createData = () => {
  const data = reactive({ value: 1 });
  
  // ❌ 危险:闭包中引用自身
  data.self = data; // 可能导致内存泄漏
  
  return data;
};

// 3. 使用 WeakRef 处理大型对象
const largeCache = new WeakMap();

const getCachedData = (key) => {
  let cached = largeCache.get(key);
  if (!cached) {
    cached = computeExpensiveData(key);
    largeCache.set(key, cached);
  }
  return cached;
};

九、实际应用案例

9.1 表单处理:规避循环依赖

vue

<template>
  <form @submit.prevent="handleSubmit">
    <div>
      <label>邮箱:</label>
      <input v-model="form.email" @blur="validateEmail" />
      <span class="error">{{ errors.email }}</span>
    </div>
    
    <div>
      <label>密码:</label>
      <input v-model="form.password" type="password" />
      <span class="error">{{ errors.password }}</span>
    </div>
    
    <button :disabled="!isValid || isSubmitting">
      {{ isSubmitting ? '提交中...' : '提交' }}
    </button>
  </form>
</template>

<script setup>
import { reactive, computed, ref } from 'vue';

// 表单数据
const form = reactive({
  email: '',
  password: '',
  confirmPassword: ''
});

// 提交状态
const isSubmitting = ref(false);

// ✅ 使用 computed 推导验证状态,避免 watch 相互依赖
const errors = computed(() => {
  const err = {};
  
  // 邮箱验证
  if (!form.email) {
    err.email = '邮箱不能为空';
  } else if (!form.email.includes('@')) {
    err.email = '邮箱格式不正确';
  }
  
  // 密码验证
  if (form.password.length < 6) {
    err.password = '密码至少6位';
  }
  
  // 确认密码
  if (form.password !== form.confirmPassword) {
    err.confirmPassword = '两次密码不一致';
  }
  
  return err;
});

// 表单是否有效
const isValid = computed(() => {
  return Object.keys(errors.value).length === 0;
});

// 单独的验证函数(用于即时验证)
const validateEmail = () => {
  if (form.email && !form.email.includes('@')) {
    // 可以在这里触发 UI 反馈
    console.log('邮箱格式错误');
  }
};

// 提交处理
const handleSubmit = async () => {
  if (!isValid.value || isSubmitting.value) return;
  
  isSubmitting.value = true;
  try {
    // 模拟 API 调用
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log('表单提交成功:', form);
  } catch (error) {
    console.error('提交失败:', error);
  } finally {
    isSubmitting.value = false;
  }
};
</script>

<style scoped>
.error {
  color: red;
  font-size: 12px;
}
</style>

9.2 实时搜索:控制触发频率

vue

<template>
  <div>
    <input 
      v-model="searchQuery" 
      placeholder="输入关键词搜索..."
      @input="handleInput"
    />
    
    <div v-if="loading">搜索中...</div>
    
    <ul v-else-if="results.length">
      <li v-for="result in results" :key="result.id">
        {{ result.title }}
      </li>
    </ul>
    
    <div v-else-if="searchQuery && !loading">
      没有找到相关结果
    </div>
  </div>
</template>

<script setup>
import { ref, watchEffect } from 'vue';

const searchQuery = ref('');
const results = ref([]);
const loading = ref(false);

// 防抖函数
const debounce = (fn, delay) => {
  let timer = null;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
};

// 搜索函数
const performSearch = async (query) => {
  if (!query.trim()) {
    results.value = [];
    return;
  }
  
  loading.value = true;
  try {
    // 模拟 API 调用
    await new Promise(resolve => setTimeout(resolve, 500));
    
    // 模拟搜索结果
    results.value = [
      { id: 1, title: `结果1: ${query}` },
      { id: 2, title: `结果2: ${query}` },
      { id: 3, title: `结果3: ${query}` }
    ];
  } catch (error) {
    console.error('搜索失败:', error);
    results.value = [];
  } finally {
    loading.value = false;
  }
};

// 使用 watchEffect 自动追踪依赖
let abortController = null;

watchEffect((onCleanup) => {
  const query = searchQuery.value.trim();
  
  // 清理之前的请求
  onCleanup(() => {
    if (abortController) {
      abortController.abort();
    }
  });
  
  if (!query) {
    results.value = [];
    return;
  }
  
  // 创建新的 AbortController
  abortController = new AbortController();
  
  // 防抖处理
  const debouncedSearch = debounce(async () => {
    await performSearch(query);
  }, 300);
  
  debouncedSearch();
});

// 输入处理
const handleInput = debounce(() => {
  // 这里可以添加其他输入处理逻辑
}, 100);
</script>

十、响应式系统的扩展:Pinia 状态管理

10.1 Pinia 与 Vue 响应式的关系

javascript

// store/user.js
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useUserStore = defineStore('user', () => {
  // 状态
  const name = ref('张三');
  const age = ref(25);
  const isLoggedIn = ref(false);
  
  // Getter(计算属性)
  const displayName = computed(() => {
    return isLoggedIn.value ? name.value : '未登录用户';
  });
  
  const isAdult = computed(() => age.value >= 18);
  
  // Action(方法)
  const login = (userName) => {
    name.value = userName;
    isLoggedIn.value = true;
  };
  
  const logout = () => {
    isLoggedIn.value = false;
  };
  
  const updateAge = (newAge) => {
    if (newAge > 0) {
      age.value = newAge;
    }
  };
  
  return {
    name,
    age,
    isLoggedIn,
    displayName,
    isAdult,
    login,
    logout,
    updateAge
  };
});

// 在组件中使用
<script setup>
import { useUserStore } from '@/stores/user';
import { storeToRefs } from 'pinia';

const userStore = useUserStore();

// 使用 storeToRefs 保持响应式
const { name, age, displayName } = storeToRefs(userStore);

// 调用 action
const handleLogin = () => {
  userStore.login('李四');
};
</script>

10.2 Pinia 的响应式原理

javascript

// Pinia 内部简化实现
function createPinia() {
  const stores = new Map();
  
  return {
    // 安装插件
    use(plugin) {
      // 插件系统
    },
    
    // 创建 store
    _s: stores,
    
    // 提供给 app 使用
    install(app) {
      app.provide('pinia', this);
    }
  };
}

// Store 的核心:响应式状态管理
function defineStore(id, setup) {
  return function useStore() {
    const pinia = inject('pinia');
    
    if (!pinia._s.has(id)) {
      // 创建响应式状态
      const setupStore = setup();
      pinia._s.set(id, setupStore);
    }
    
    return pinia._s.get(id);
  };
}

十一、服务端渲染(SSR)中的响应式

11.1 SSR 环境下的响应式注意事项

javascript

// 在 SSR 环境中使用响应式
import { createSSRApp } from 'vue';
import { reactive, ref, onMounted } from 'vue';

// 只在客户端执行的代码
const useClientOnlyLogic = () => {
  const windowWidth = ref(0);
  
  onMounted(() => {
    // 仅在客户端访问 window
    windowWidth.value = window.innerWidth;
    
    window.addEventListener('resize', () => {
      windowWidth.value = window.innerWidth;
    });
  });
  
  return { windowWidth };
};

// 检查运行环境
const isClient = typeof window !== 'undefined';
const isServer = !isClient;

// 条件性使用响应式 API
const useResponsiveData = () => {
  const data = ref(null);
  
  if (isClient) {
    // 只在客户端获取数据
    fetchData().then(result => {
      data.value = result;
    });
  }
  
  return { data };
};

// SSR 友善的组件
export default {
  setup() {
    // 响应式数据在服务端也会被创建
    const count = ref(0);
    
    // 但副作用只在客户端执行
    onMounted(() => {
      console.log('只在客户端执行');
    });
    
    return { count };
  },
  
  // 服务器端渲染时的特殊处理
  async serverPrefetch() {
    // 在服务器端获取数据
    const data = await fetchServerData();
    this.count = data.count;
  }
};

11.2 响应式数据的序列化

javascript

// 服务器端
import { renderToString } from '@vue/server-renderer';
import { createSSRApp } from 'vue';
import App from './App.vue';

async function renderApp(url) {
  const app = createSSRApp(App);
  
  // 设置服务器端状态
  const initialState = {
    user: { name: '张三', age: 25 },
    settings: { theme: 'dark' }
  };
  
  // 将状态注入到应用
  app.provide('initialState', initialState);
  
  const html = await renderToString(app);
  
  // 将状态序列化到 HTML
  const serializedState = JSON.stringify(initialState);
  
  return `
    <html>
      <head><title>SSR App</title></head>
      <body>
        <div id="app">${html}</div>
        <script>
          window.__INITIAL_STATE__ = ${serializedState};
        </script>
        <script src="/client.js"></script>
      </body>
    </html>
  `;
}

// 客户端
import { createSSRApp } from 'vue';
import App from './App.vue';

if (typeof window !== 'undefined' && window.__INITIAL_STATE__) {
  const app = createSSRApp(App);
  
  // 恢复服务器端状态
  app.provide('initialState', window.__INITIAL_STATE__);
  
  app.mount('#app');
}

十二、调试技巧:使用 Vue DevTools 分析响应式

12.1 安装与配置

bash

# 安装 Vue DevTools
npm install -D @vue/devtools

# 或使用浏览器扩展
# Chrome Web Store: Vue.js devtools

12.2 响应式调试功能

12.2.1 查看组件树与状态

text

Vue DevTools → Components 标签:
├── 查看组件层级结构
├── 查看组件的 props、data、computed
├── 查看组件实例的响应式状态
└── 实时编辑状态值

12.2.2 时间旅行调试

javascript

// 在开发环境中启用时间旅行
import { createApp } from 'vue';

const app = createApp(App);

if (process.env.NODE_ENV === 'development') {
  // 启用 DevTools
  app.config.devtools = true;
  
  // 记录状态变化
  app.config.performance = true;
}

// 在 DevTools 中:
// 1. 打开 Timeline 标签
// 2. 记录状态变化
// 3. 使用时间轴回溯状态

12.2.3 依赖关系分析

javascript

// 自定义调试函数
function debugReactiveDependencies(target, key) {
  if (!targetMap) {
    console.warn('targetMap 不可访问(生产环境)');
    return;
  }
  
  const depsMap = targetMap.get(target);
  if (!depsMap) {
    console.log(`没有找到 ${target} 的依赖映射`);
    return;
  }
  
  const dep = depsMap.get(key);
  if (!dep) {
    console.log(`没有找到 ${key} 属性的依赖`);
    return;
  }
  
  console.group(`${target.constructor.name}.${key} 的依赖关系`);
  console.log(`依赖函数数量: ${dep.subscribers.size}`);
  
  dep.subscribers.forEach((effect, index) => {
    console.log(`[${index + 1}]`, effect.toString().slice(0, 100) + '...');
  });
  
  console.groupEnd();
}

// 使用示例
const user = reactive({ name: '张三', age: 25 });
debugReactiveDependencies(user, 'name');

12.3 性能分析

javascript

// 监控响应式性能
import { watchEffect } from 'vue';

let effectCount = 0;
const maxEffects = 100;

const monitoredEffect = (fn, name = 'unnamed') => {
  return watchEffect(() => {
    effectCount++;
    const start = performance.now();
    
    try {
      fn();
    } finally {
      const duration = performance.now() - start;
      
      if (duration > 16) { // 超过一帧的时间
        console.warn(`[性能警告] ${name} 执行时间: ${duration.toFixed(2)}ms`);
      }
      
      if (effectCount > maxEffects) {
        console.error(`[循环依赖警告] ${name} 执行次数: ${effectCount}`);
        effectCount = 0;
      }
    }
  });
};

// 使用监控版的 watchEffect
monitoredEffect(() => {
  console.log('监控中的副作用');
}, 'myEffect');

十三、总结与核心洞察

13.1 Vue 响应式系统的三要素

  1. 响应式数据:通过 ref、reactive 创建可追踪的数据
  2. 依赖追踪:利用全局依赖映射表自动建立数据与函数的关联
  3. 自动更新:数据变化时精准触发相关的副作用函数

13.2 全局依赖映射表的核心作用

text

WeakMap<target, Map<key, Dep>> 三层结构:
├── 第一层:WeakMap(按对象组织,自动内存管理)
├── 第二层:Map(按属性组织,细粒度追踪)
└── 第三层:Dep(依赖集合,存储副作用函数)

13.3 关键设计决策对比

决策点

Vue 3 选择

优势

注意事项

代理方式

Proxy

支持数组、嵌套对象、动态属性

不兼容 IE11

API 设计

组合式 API

逻辑复用更灵活

学习曲线较陡

更新策略

异步批量更新

性能优化,避免重复渲染

需要 nextTick 访问 DOM

内存管理

WeakMap 自动清理

减少内存泄漏风险

需注意循环引用

13.4 开发者必须掌握的核心

  1. 理解响应式原理:重点掌握依赖映射表的构建与触发逻辑
  2. 区分循环依赖场景:重点防范相互/网状依赖,优先通过单向数据流规避
  3. 选择合适的 API:根据依赖明确性、循环风险选择 watch/watchEffect
  4. 遵循最佳实践:合理使用计算属性缓存、控制响应式深度

13.5 响应式编程的本质

Vue 的响应式系统通过观察者模式的现代化实现,完成了从”命令式 DOM 操作”到”声明式数据驱动”的根本转变。全局依赖映射表的设计让系统具备自动追踪、精准更新、高效管理的核心能力。

核心原则:响应式编程是一种思维方式,而非单纯的 API 集合。当理解了”数据驱动视图”的哲学和全局依赖映射表的设计逻辑,就能真正驾驭 Vue 的响应式系统,构建高性能、可维护的前端应用。


附录:快速参考手册

响应式 API 速查表

API

用途

返回值

是否递归

模板是否需要 .value

ref(value)

创建响应式引用

Ref 对象

对象/数组会递归

需要(模板中自动解包)

reactive(obj)

创建响应式对象

Proxy 对象

不需要

computed(getter)

创建计算属性

只读 Ref

需要(模板中自动解包)

watch(source, callback)

侦听变化

停止函数

可配置 deep

watchEffect(effect)

自动追踪依赖

停止函数

自动追踪

toRef(obj, key)

属性转 ref

Ref 对象

需要

toRefs(obj)

对象转 refs

包含 ref 的对象

需要

shallowRef(value)

浅层 ref

Ref 对象

需要

shallowReactive(obj)

浅层 reactive

Proxy 对象

不需要

markRaw(obj)

标记非响应式

原对象

常见问题排查表

问题现象

可能缘由

解决方案

数据变化视图不更新

1. 直接修改了 reactive 的某个属性
2. 数组通过索引修改
3. 添加了未声明的属性

1. 使用响应式 API 修改
2. 使用数组方法或替换整个数组
3. 使用 set 或预先声明

无限循环/频繁更新

1. 副作用函数修改自身依赖
2. 多个 watch 相互触发

1. 添加终止条件
2. 改用 computed 或单向数据流

内存占用过高

1. 大型对象使用 reactive
2. 未清理的副作用

1. 使用 shallowReactive 或手动控制
2. 及时调用停止函数

SSR 报错

在服务端访问了客户端 API

使用条件判断:if (typeof window !== 'undefined')

性能优化检查清单

  • 大型对象是否使用 shallowReactive/shallowRef
  • 常量数据是否使用 markRaw
  • 频繁计算的衍生数据是否使用 computed
  • 数组更新是否使用高效方法(而非索引修改)
  • 副作用函数是否及时清理
  • 是否避免了不必要的深度监听
  • 是否使用了防抖/节流控制触发频率
  • 是否检查过循环依赖风险
© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容