861 字
4 分钟
Vue 3 响应式基础与实现原理

TodoList 案例#

下面这个组件把响应式的几个基本用法都用上了,后面的原理都围绕它展开。

TodoList.vue
<script setup lang="ts">
import { ref } from 'vue'
interface Todo {
id: number
content: string
completed: boolean
}
// 输入框绑定的新任务内容
const newTodo = ref('')
const todos = ref<Todo[]>([])
function addTodo() {
const value = newTodo.value.trim()
if (!value) return
todos.value.push({
id: Date.now(),
content: value,
completed: false,
})
newTodo.value = ''
}
function handleChange(id: number) {
// find 的谓词返回布尔真值;find 本身返回匹配到的元素,找不到则返回 undefined
const item = todos.value.find((todo) => todo.id === id)
if (item) {
item.completed = !item.completed
}
}
</script>
<template>
<div class="todo-app">
<h1>Todolist</h1>
<label>
<input type="text" v-model="newTodo" />
<button @click="addTodo()">添加任务</button>
</label>
<h2>待办清单</h2>
<ul>
<li v-for="todo in todos" :key="todo.id">
<span :class="{ completed: todo.completed }">{{ todo.content }}</span>
<input
type="checkbox"
:checked="todo.completed"
@change="handleChange(todo.id)"
/>
</li>
</ul>
</div>
</template>
<style scoped>
.completed {
text-decoration: line-through;
}
.todo-app {
width: 200px;
height: 200px;
margin: 100px auto;
text-align: center;
}
button {
margin-top: 10px;
}
</style>

响应式要解决的问题#

响应式指数据变化时,依赖这份数据的代码自动重新执行。

用一句话说:你改 count.value,模板里的 {{ count }} 自己就变了,不需要手动操作 DOM。

Vue 3 的实现由三部分组成:Proxy、依赖收集和触发更新。

track 与 trigger#

track(收集依赖):读取数据时,记下这次读取发生在哪个副作用里。组件渲染函数、computed 和 watch 都算副作用。

trigger(触发更新):修改数据时,找出所有依赖这份数据的副作用,让它们重新执行。

两者合起来就是「数据变 → 视图自动更新」。

ref:包装任意值#

ref() 接收一个值,返回一个带 .value 属性的响应式对象。

const count = ref(0)
count.value // 0
count.value = 1 // 触发更新

内部实现是一个 class RefImpl,用 get value() 和 set value() 两个存取器拦截读写:读取时调用 track 收集依赖,写入时调用 trigger 触发更新。

ref 能装的值不限类型。放进去的是对象时,ref 会把它交给 reactive() 做深度转换。

reactive:代理对象与数组#

reactive() 接收一个对象,返回它的 Proxy 代理。

const state = reactive({ count: 1 })
state.count++ // 触发更新,不需要 .value

代理会拦截 get、set、deleteProperty 等操作:读的时候收集依赖,改和删的时候触发更新。

接受的入参包括普通对象、数组,以及 Map、Set、WeakMap、WeakSet 这些集合类型。

为什么还需要 ref#

reactive() 只对对象生效。原始类型不是对象,Proxy 代理不了。

ref 的思路是把值包进一个对象,再用存取器监听这个对象的读写,绕开了这条限制。

reactive 的两个边界#

第一,深度是默认的。 reactive() 是深度响应式,访问嵌套对象时会递归把它也转成 Proxy,所以改 state.friend.name 同样能触发更新。

第二,不能整体重新赋值。

let state = reactive({ count: 1 })
state = reactive({ count: 2 }) // 丢失响应式

第 2 篇,共 19 篇本文属于系列

Vue 3 响应式基础与实现原理
https://www.choria.top/posts/vue-basic/
作者
乔瑞雅·克林威尔
发布于
2026-04-03
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 219 天

部分内容可能已过时