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

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

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的最新值

我们会发现,只要 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
ts 复制代码
export let activeSub = undefined

export function effect(fn) {
    activeSub = fn     // 标记当前正在运行的副作用函数
    fn()               // 立即执行一次
    activeSub = undefined
}
  1. Ref 实现:发布者 + 注册表
ts 复制代码
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

js 复制代码
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',这是本地打包的产物。

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

总结

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

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

相关推荐
leoZ2317 小时前
Claude 驱动的全栈开发:Spring Boot + Vue 的 BS 架构实践
vue.js·spring boot·架构
荒诞英雄11 小时前
从 17MB Vendor 大包到按需加载:Vite 多入口 Vue 组件库首屏优化实践
vue.js·vite
jsonbro11 小时前
手把手带你实现发布订阅模式
前端·vue.js·设计模式
Cobyte13 小时前
23.Vue Vapor 组件 attrs 的实现
前端·javascript·vue.js
斯特凡今天也很帅14 小时前
xml页面可以打开,不报错,但是显示数据的地方也不显示,我是怎么解决的
xml·vue.js
柯克七七16 小时前
给老项目加了 TypeScript,本来只想自己爽,结果全公司代码审查标准被我抬高了
前端·vue.js·typescript
自然 醒18 小时前
前端如何实现在线预览office常见文件功能?
前端·vue.js
肉肉不吃 肉18 小时前
前端调试跨域如何解决
前端·vue.js
卓怡学长18 小时前
w268基于springboot + vue 物流系统
java·数据库·vue.js·spring boot·spring·intellij-idea
2601_9628517419 小时前
计算机毕业设计之基于SSM的网络安全课程资源分享平台
vue.js·算法·安全·web安全·yolo·目标检测·课程设计