Vue3的`effect`函数是如何驱动组件重新渲染的?

阿乐
阿乐 管理员 黑卡会员
发布于 2026-09-09 18:28 ·2 浏览 ·0 回复

在 Vue3 的源码里,`effect` 是一个看似不起眼却贯穿整个响应式系统的核心函数。如果你曾经好奇过:当我修改一个 `ref` 或者 `reactive` 对象时,为什么页面上的组件会自动更新?答案就藏在 `effect` 与组件渲染函数的绑定关系中。今天我们不啃晦涩的源码,而是从“现象”出发,一步步拆解这个驱动机制。

从渲染函数说起

每个 Vue 组件在挂载时,都会执行一次 `render` 函数来生成虚拟 DOM。但 `render` 函数并不是被直接调用的,它被包裹在一个 `effect` 里。也就是说,组件实例的 `update` 方法本质上就是一个 `effect` 的 runner。

const componentEffect = () => {
  // 执行 render,生成 vnode
  const vnode = component.render()
  // 后续 patch 逻辑...
}

当这个 `effect` 第一次执行时,它会读取模板中用到的所有响应式数据(比如 `count.value`)。而响应式数据的 `track` 机制会把这个 `effect` 记录为当前依赖。于是,组件和这些数据之间建立了一条“订阅关系”。

数据变化时的通知链

当你执行 `count.value++` 时,Vue 内部会触发 `trigger`。此时,所有订阅了 `count` 的 `effect` 都会被放入一个待执行队列——注意,不是直接同步执行,而是通过微任务队列进行调度。这种异步批处理的设计,保证了你在同一个事件循环里多次修改数据,组件只会重新渲染一次。

我画了一个简化流程:

1. 修改响应式数据 → `trigger` 通知相关 effects
2. effect 被标记为 dirty,并推入调度器队列
3. 微任务执行时,调度器取出 effect,调用 runner
4. runner 再次执行渲染逻辑,生成新 vnode
5. 与旧 vnode 进行 diff,并更新真实 DOM

为什么是 `effect` 而不是 `watch`?

很多初学者会把 `effect` 和 `watchEffect` 混淆。实际上,`watchEffect` 就是基于 `effect` 实现的更高层 API。组件渲染用的就是底层的 `effect` 实例(`ReactiveEffect`),只不过它在创建时传入了一个 `scheduler` 调度函数,而不是简单的 `fn`。

这个 `scheduler` 的关键作用是:避免每次数据变化都同步执行更新,而是把更新任务交给调度器。这使得 Vue 可以支持 `nextTick`、`flush: 'post'` 这类特性,也让用户在使用组件时获得更平滑的渲染体验。

来看一个简化版的伪代码:

class ReactiveEffect {
  constructor(fn, scheduler) {
    this.fn = fn
    this.scheduler = scheduler
  }
  run() {
    // 执行时收集依赖
    activeEffect = this
    return this.fn()
  }
  trigger() {
    if (this.scheduler) {
      this.scheduler(this)
    } else {
      this.run()
    }
  }
}

组件更新时,Vue 传入的 `scheduler` 会把 `job` 加入队列,并在 `nextTick` 的 promise 解析后统一执行。

effect 的“嵌套”与渲染时机

还有一个有趣的现象:组件树本身是嵌套的,所以 `effect` 的执行也会有嵌套。Vue 3 维护了一个 `effectStack`,用来在嵌套执行时正确还原 `activeEffect`。否则,当父组件的 effect 读取子组件的响应式数据时,依赖关系会错乱。

这个设计细节直接保证了:即使在父组件渲染函数中访问了子组件的 `setup` 返回的 `ref`,依赖也能被正确追踪,不会出现“改子组件数据却更新父组件”的诡异情况。

总结

`effect` 是 Vue 3 响应式系统的心脏。它将组件的渲染函数转变为可追踪、可调度、可取消的“副作用任务”。数据只是触发信号,真正的更新动作由 `effect` 统一管理。理解了 `effect`,你就理解了 Vue 3 的更新机制,再去看 `computed`、`watch`、乃至 `pinia` 的底层实现都会豁然开朗。

想深入的话,不妨打开 `@vue/reactivity` 源码,搜索 `ReactiveEffect` 这个类,用断点慢慢跟踪一次数据更新。相信你会有种“原来是这么回事”的爽快感。毕竟,看源码不如跑源码,跑源码不如跟着 effect 走一遍链路。

本文转载自 阿乐技术社区,原文地址:https://www.leleweb.cn/thread-175.html
转载请注明出处,版权归原作者所有。

全部回复 0

还没有回复,来抢沙发~