普通视图

发现新文章,点击刷新页面。
今天 — 2025年12月10日首页

Vue3 源码学习笔记(二): 理解发布-订阅模式和实现简易响应式

2025年12月10日 01:15

当我们导入 vue 3 的特定构建版本,在浏览器打开html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>响应式demo</title>
</head>
<body>
<script type="module">
    import {ref, effect } from '../../../node_modules/vue/dist/vue.esm-browser.prod.js'
    
    const count = ref(0)
    effect (()=> {
        console.log('count.value ==>',count.value)
    })
    setTimeout(()=>{
        count.value = 1
    }, 1000)
</script>
</body>
</html>

控制台会在1s后打印count的最新值

image.png

我们会发现,只要 count 的值变了,副作用函数就会自动重新执行。这背后的原因是什么?

我们知道 vue3 的响应式系统本质就是一个高度自动化的发布-订阅模式(Publish-Subscribe Pattern)

什么是发布-订阅模式(Publish-Subscribe Pattern)

  • 以点外卖为例
    • 发布者 = 餐厅的菜品

      • 持有状态:餐厅掌握着所有菜的状态
      • 发布通知:当菜品状态变化时,会自动发出"我变了"的信号
      • 特点:菜品不知道自己被谁关注,只负责在变化时发出信号
    • 订阅者 = 顾客的查看行为

      • 关注状态:顾客在执行点餐流程时,会查看某些菜品
      • 希望被通知:当关注的菜品变化时,希望重新执行点餐流程
      • 特点:只关心自己查看过的菜品,不关心其他菜品
    • 事件通道 = 外卖平台

      • 记录关系:当顾客查看菜品时,平台记录"顾客A关注了菜品X" (← 依赖收集
      • 桥梁作用:餐厅更新菜品状态,然后通知这些顾客重新执行点餐流程 (← 执行effect)

实现响应式

packages/reactivity/src 下新建ref.tseffect.ts 并且在 index.ts 导出。

我们要实现两个核心 API:

  • ref(value):创建一个响应式引用
  • effect(fn):创建一个副作用函数,自动追踪依赖
  1. 全局变量:记录当前正在执行的 effect
export let activeSub = undefined

export function effect(fn) {
    activeSub = fn     // 标记当前正在运行的副作用函数
    fn()               // 立即执行一次
    activeSub = undefined
}
  1. Ref 实现:发布者 + 注册表
import {activeSub} from "./effect";

enum ReactiveFlags {
    IS_REF = '__V_isRef'
}

/**
 * Ref实现类
 */
class RefImpl {
    // 保存实际的值
    _value
    // ref标记,证明是一个ref
   [ReactiveFlags.IS_REF] = true
    // 保存和 effect 之间的关联关系
    subs
    constructor(value) {
        this._value = value
    }

    /**
     * 依赖收集
     */
    get value() {
        // 如果 activeSub 有就保存起来,等更新时触发
        if(activeSub) {
            this.subs = activeSub
        }
        return this._value
    }

    /**
     * 触发更新
     * @param newVal
     */
    set value(newVal) {
        console.log('==>触发更新咯')
        this._value = newVal
        // 通知 effect 重新执行,获取最新的值
        this.subs?.()
    }
}


export function  ref(value) {
    return new RefImpl(value)
}

/**
 * 判断是不是一个 ref
 * @param value
 */
export function  isRef (value) {
    return !!(value && value[ReactiveFlags.IS_REF])
}

执行流程

步骤 1:调用 effect

effect(() => { console.log(count.value) })
  • 设置 activeSub = fn
  • 执行fn → 读取 count.value

步骤 2:读取 count.value(订阅发生)

  • 进入 RefImpl.get value
  • 发现 activeSub 存在 → 将当前函数存入 subs
  • 👉 完成订阅count 记住了“谁在用我”

步骤 3:修改 count.value = 1(发布发生)

  • 进入 RefImpl.set value
  • 更新 _value
  • 调用 this.subs?.() → 重新执行副作用函数
  • 👉 完成发布:通知订阅者“我变了!”

验证效果

将html文件的引入替换成import {ref, effect } from '../dist/reactivity.esm.js',这是本地打包的产物。

image.png 此时,实现了响应式的简易版本。

总结

  • Vue 3 的响应式系统本质是 隐式的发布-订阅模式
  • ref 是 发布者effect 是 订阅者.subs 是 注册表
  • 读取即订阅,修改即发布 —— 这就是响应式的魔法。

想了解更多 Vue 的相关知识,抖音、B站搜索远方os

❌
❌