Vue 3 的响应式不是定时扫描数据,而是在代码运行时记录“谁读取了什么”,并在数据变化时通知相关计算和视图更新。普通对象通过 Proxy 拦截属性读取与写入;ref 则用带有 .value 的对象拦截读写。JavaScript 无法直接监听普通局部变量的赋值,因此 ref 需要一个可被拦截的容器。Vue:深入响应式原理

可以把一次响应式更新理解为三个步骤:
computed 求值期间,读取 state.count,Vue 记录当前运行的渲染任务或计算任务依赖这个属性。state.count++ 后,Vue 找到依赖该属性的任务,并安排它们重新运行。nextTick。Vue:响应式基础实现上,可以粗略想象成“对象 → 属性 → 依赖任务”的索引表。实际逻辑还要处理新增、删除、数组和集合迭代等情况,也会在任务重新运行时更新依赖关系。比如 if (showA) total = a.value; else total = b.value,条件变化后,total 应改为依赖当前分支读取的数据,而不是一直监听两边。
computed 也依赖这套机制:它会记录计算过程中读取的响应式数据,依赖没变时复用缓存,依赖变化后才重新计算。watch 和 watchEffect 则适合把响应式变化连接到副作用,例如请求数据、同步第三方库或写入浏览器存储。副作用应放在合适的监听逻辑中;单纯展示派生值时,用 computed 通常更直接。
ref 适合保存基本类型,也能包装对象;对象值仍可深层响应。reactive 直接把对象变成代理,适合表达一组有结构的状态,但有几个限制:不能直接用基本类型;整体换掉变量会让原有代理引用失效;从代理中解构出基本类型时,局部变量不会继续跟踪源属性。Vue 文档因此建议在组合式 API 中优先考虑 ref。Vue:响应式基础
const state = reactive({ count: 0 })
const { count } = state // count 是普通数值,之后不会随 state.count 更新
const countRef = toRef(state, 'count') // 保留对该属性的响应式引用
大型、整体替换的数据,或由外部系统维护的数据,可考虑 shallowRef 等浅层 API,避免不必要的深层代理;代价是内部属性变化不会像深层响应式对象那样自动触发更新,需要按其更新方式替换 .value 或显式触发。响应式代理与原始对象也不是同一个身份,比较引用时不要假定 reactive(raw) === raw。
Pinia 把跨组件共享的数据和操作集中到 store 中。一个 store 通常包含三类内容:state 保存事实数据,getters 表达派生结果,actions 承担业务操作。它们分别近似组件中的 data、computed 和 methods,但作用范围可以跨组件。Pinia:定义 Store
export const useCartStore = defineStore('cart', {
state: => ({
items: []
})
getters: {
itemCount: (state) => state.items.reduce((sum, item) => sum + item.quantity, 0)
}
actions: {
addItem(product) {
const item = this.items.find((item) => item.id === product.id)
if (item) item.quantity++
else this.items.push({ ...product, quantity: 1 })
}
}
})
state 使用函数返回初始对象,使每个应用实例能获得自己的状态;需要管理的字段应在 state 中预先声明。getters 适合复用计算结果,不应拿来执行有副作用的业务流程。actions 可以同步或异步,适合封装规则和流程,例如校验、调用接口后更新状态。组件无需复制一份共享数据,只需取得 store 并读写它的属性。
直接使用 store.items 会保持响应式;但从 store 解构状态或 getter,会像解构普通响应式对象一样丢失属性连接。需要解构时使用 storeToRefs;action 可以直接解构,因为它绑定到 store 实例。Pinia:定义 Store
const cart = useCartStore
const { items, itemCount } = storeToRefs(cart)
const { addItem } = cart
状态归属应清晰:只在单个组件内使用的输入框展开状态,通常放在组件里;多个页面都需要的用户信息、购物车或筛选条件,才适合进入共享 store。不要把所有临时数据都塞进全局状态,否则状态来源和更新路径会变得难以追踪。
Pinia 也提供 $subscribe 监听状态变化、$onAction 观察 action 调用等能力,可用于持久化或调试;它们不会自动替代业务设计。服务端渲染时,应确保每个请求使用独立的应用和 Pinia 实例,避免请求间共享用户状态。把响应式原理和状态边界想清楚后,Pinia 的价值就很明确:它负责组织共享状态,Vue 则负责追踪这些状态被谁使用,并在状态变化时更新相关视图。