派生状态与 computed
React 在渲染期间从现有状态计算结果;Vue 使用 computed 声明缓存的派生值。
核心结论
可以从现有 props/state 推导的值,不要再存一份可变状态。React 通常在 render 中直接计算;Vue 通常用 computed 表达响应式派生关系。
单一事实来源
组件关系与数据流
StateComponentDerivedData
差异焦点没有缓存或订阅关系,派生计算跟随每次组件 render 执行。
实线表示依赖、数据或命令传递,虚线表示事件、回调或清理路径。
flowchart TB
accTitle: React 渲染期间直接派生
accDescr: query 或 inStockOnly 更新后 ReactDemo 函数重新执行,products filter 在每次 render 中直接运行并生成普通 visibleProducts 值。
filters(["query + inStockOnly state"]) --> render["ReactDemo 再次执行"]
products[("products")] --> render
render --> derive{{"每次 render 执行 filter"}}
derive --> result[("普通 visibleProducts 值")]
result --> list["商品列表 UI"]
class filters state
class render,list component
class derive process
class products,result resource
classDef state fill:#fffdf8,stroke:#149eca,color:#25221d,stroke-width:1.2px
classDef component fill:#e4f5fa,stroke:#149eca,color:#25221d,stroke-width:2px,font-weight:600
classDef process fill:#f4fafb,stroke:#149eca,color:#25221d,stroke-width:1.6px
classDef resource fill:#eee9df,stroke:#817a70,color:#25221d,stroke-width:1.4px组件重新执行时直接 filter;visibleProducts 是本次 render 的普通值。
组件关系与数据流
StateComponentDerivedData
差异焦点computed 是可缓存的响应式派生值,不依赖组件整体重新执行。
实线表示依赖、数据或命令传递,虚线表示事件、回调或清理路径。
flowchart TB
accTitle: Vue computed 响应式派生
accDescr: computed 首次读取时自动收集 query 与 inStockOnly refs,依赖未变时返回缓存,依赖变化后重新计算 visibleProducts 并更新列表。
filters(["query + inStockOnly refs"]) --> tracking{{"computed 自动收集依赖"}}
products[("products")] --> tracking
tracking --> cache[("缓存 visibleProducts")]
cache -->|"依赖未变直接复用"| list["商品列表 UI"]
class filters state
class list component
class tracking process
class products,cache resource
classDef state fill:#fffdf8,stroke:#2f9d6b,color:#25221d,stroke-width:1.2px
classDef component fill:#e4f4eb,stroke:#2f9d6b,color:#25221d,stroke-width:2px,font-weight:600
classDef process fill:#f2f8f4,stroke:#2f9d6b,color:#25221d,stroke-width:1.6px
classDef resource fill:#eee9df,stroke:#817a70,color:#25221d,stroke-width:1.4pxcomputed 自动追踪响应式读取,并在依赖未变化时复用缓存。
完整 Demo 源码
以下代码直接读取在线 Demo 使用的源文件,没有省略或改写。
React 源码
import { useState } from "react";const products = [ { id: 1, name: "Apple", price: "$1", stocked: true }, { id: 2, name: "Dragonfruit", price: "$1", stocked: true }, { id: 3, name: "Passionfruit", price: "$2", stocked: false }, { id: 4, name: "Spinach", price: "$2", stocked: true },];export function ReactDemo() { const [query, setQuery] = useState(""); const [inStockOnly, setInStockOnly] = useState(false); const visibleProducts = products.filter((product) => { const matchesQuery = product.name .toLowerCase() .includes(query.toLowerCase()); return matchesQuery && (!inStockOnly || product.stocked); }); return ( <div className="demo-stack"> <label className="demo-field"> 搜索商品 <input className="demo-input" value={query} placeholder="例如 Apple" onChange={(event) => setQuery(event.target.value)} /> </label> <label className="demo-field"> <span> <input type="checkbox" checked={inStockOnly} onChange={(event) => setInStockOnly(event.target.checked)} />{" "} 仅显示有库存 </span> </label> <ul className="demo-list"> {visibleProducts.map((product) => ( <li key={product.id}> <span>{product.name}</span> <span className={product.stocked ? "" : "demo-muted"}> {product.price} · {product.stocked ? "有库存" : "缺货"} </span> </li> ))} </ul> </div> );}Vue 3 源码
<script setup lang="ts">import { computed, ref } from "vue";const products = [ { id: 1, name: "Apple", price: "$1", stocked: true }, { id: 2, name: "Dragonfruit", price: "$1", stocked: true }, { id: 3, name: "Passionfruit", price: "$2", stocked: false }, { id: 4, name: "Spinach", price: "$2", stocked: true },];const query = ref("");const inStockOnly = ref(false);const visibleProducts = computed(() => products.filter((product) => { const matchesQuery = product.name .toLowerCase() .includes(query.value.toLowerCase()); return matchesQuery && (!inStockOnly.value || product.stocked); }),);</script><template> <div class="demo-stack"> <label class="demo-field"> 搜索商品 <input v-model="query" class="demo-input" placeholder="例如 Apple" /> </label> <label class="demo-field"> <span> <input v-model="inStockOnly" type="checkbox" /> 仅显示有库存 </span> </label> <ul class="demo-list"> <li v-for="product in visibleProducts" :key="product.id"> <span>{{ product.name }}</span> <span :class="{ 'demo-muted': !product.stocked }"> {{ product.price }} · {{ product.stocked ? "有库存" : "缺货" }} </span> </li> </ul> </div></template>在线观察
输入搜索词并切换“仅显示有库存”,两个 Demo 都只保存原始商品与筛选条件。
React
- Apple$1 · 有库存
- Dragonfruit$1 · 有库存
- Passionfruit$2 · 缺货
- Spinach$2 · 有库存
Vue 3
- Apple$1 · 有库存
- Dragonfruit$1 · 有库存
- Passionfruit$2 · 缺货
- Spinach$2 · 有库存
为什么不再存一份列表
如果把 visibleProducts 也放进 state,就必须在每次来源变化时保持同步,容易产生过期数据和多余 Effect。派生值应尽量保持纯函数关系。
派生不等于缓存
React 中直接计算首先解决数据一致性;只有计算成本已经成为问题时,才进一步使用 useMemo。Vue 的 computed 同时表达派生关系并提供缓存。
昂贵计算继续阅读useMemo 与 computed;状态更新基础见useState 与 ref/reactive。