当我们导入 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.ts 和 effect.ts 并且在 index.ts 导出。
我们要实现两个核心 API:
ref(value):创建一个响应式引用effect(fn):创建一个副作用函数,自动追踪依赖
- 全局变量:记录当前正在执行的
effect
ts
export let activeSub = undefined
export function effect(fn) {
activeSub = fn // 标记当前正在运行的副作用函数
fn() // 立即执行一次
activeSub = undefined
}
- 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