useCallback 与 Vue 普通函数
React 使用 useCallback 稳定回调引用;Vue setup 中声明的普通函数天然保持稳定。
核心结论
React 组件每次 render 都会创建新的函数对象,useCallback 可以按依赖复用函数引用;Vue setup 通常只运行一次,其中声明的普通函数天然稳定。稳定引用只有在下游会比较引用时才有价值。
引用比较
组件关系与数据流
StateComponent
差异焦点函数组件反复执行,需要 useCallback 配合 memo 维持 props 引用稳定。
实线表示数据向下传递,虚线表示子组件向父组件发出通知。
flowchart TB
accTitle: React useCallback 与 memo 引用比较
accDescr: 任意 state 更新都会重新执行 ReactDemo,useCallback 根据依赖数组复用 handleSelect 引用,memo 再浅比较 props 决定是否跳过 ProductList 渲染。
state(["selectedProduct + cartCount"]) --> render["ReactDemo 每次 state 更新都执行"]
render --> callback{{"useCallback([]) 复用函数引用"}}
render --> compare{{"memo 浅比较全部 props"}}
callback --> compare
compare -->|"props 变化才执行"| child["ProductList (memo)"]
child -.->|"onSelect(product)"| state
class state state
class render,child component
class callback,compare process
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组件每次 state 更新都会执行;useCallback 稳定函数引用,memo 再比较 props。
组件关系与数据流
StateComponent
差异焦点setup 通常只执行一次,普通函数引用天然稳定,不需要对应 Hook。
实线表示数据向下传递,虚线表示子组件向父组件发出通知。
flowchart TB
accTitle: Vue setup 函数稳定性与响应式更新
accDescr: setup 通常只运行一次并创建稳定的 handleSelect,selectedProduct ref 单独驱动 selectedId prop,cartCount ref 不会改变 ProductList 的 props。
setup{{"setup 只执行一次"}} --> handler[("稳定 handleSelect 函数")]
selected(["selectedProduct ref"]) -->|"selectedId prop"| child["ProductList"]
cart(["cartCount ref"]) --> parent["VueDemo 自身模板"]
handler -->|"@select listener"| child
child -.->|"emit select(product)"| selected
class selected,cart state
class parent,child component
class setup process
class handler 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.4pxsetup 创建一次普通函数;响应式依赖分别更新父模板和子组件 props。
完整 Demo 源码
以下代码直接读取在线 Demo 使用的源文件,没有省略或改写。
React 源码
import { useCallback, useState } from "react";import { ProductList, type Product } from "./ProductList";export function ReactDemo() { const [cartCount, setCartCount] = useState(0); const [selectedProduct, setSelectedProduct] = useState<Product | null>(null); const handleSelect = useCallback((product: Product) => { setSelectedProduct(product); }, []); return ( <div className="demo-stack"> <ProductList selectedId={selectedProduct?.id ?? null} onSelect={handleSelect} /> <div className="demo-card"> <span className="demo-tag">当前选择</span> <h2>{selectedProduct?.name ?? "尚未选择商品"}</h2> <p> {selectedProduct ? `价格 ¥${selectedProduct.price}` : "点击列表中的按钮选择商品。"} </p> </div> <div className="demo-actions"> <button className="demo-button" type="button" onClick={() => setCartCount((count) => count + 1)} > 购物车 +1(当前 {cartCount}) </button> </div> <p className="demo-muted"> 更新购物车会重新渲染父组件,但稳定的 onSelect 不会破坏 memo 缓存。 </p> </div> );}Vue 3 源码
<script setup lang="ts">import { ref } from "vue";import ProductList from "./ProductList.vue";type Product = { id: number; name: string; price: number;};const cartCount = ref(0);const selectedProduct = ref<Product | null>(null);function handleSelect(product: Product) { selectedProduct.value = product;}</script><template> <div class="demo-stack"> <ProductList :selected-id="selectedProduct?.id ?? null" @select="handleSelect" /> <div class="demo-card"> <span class="demo-tag">当前选择</span> <h2>{{ selectedProduct?.name ?? "尚未选择商品" }}</h2> <p> {{ selectedProduct ? `价格 ¥${selectedProduct.price}` : "点击列表中的按钮选择商品。" }} </p> </div> <div class="demo-actions"> <button class="demo-button" type="button" @click="cartCount++"> 购物车 +1(当前 {{ cartCount }}) </button> </div> <p class="demo-muted"> 更新购物车不会改变子组件的 props,Vue 会跳过不必要的子组件更新。 </p> </div></template>在线观察
增加购物车数量只更新父组件的无关状态,不应让商品列表进行不必要的工作。
React
- 机械键盘 · ¥499
- 无线鼠标 · ¥299
- 显示器支架 · ¥359
当前选择
尚未选择商品
点击列表中的按钮选择商品。
更新购物车会重新渲染父组件,但稳定的 onSelect 不会破坏 memo 缓存。
Vue 3
- 机械键盘 · ¥499
- 无线鼠标 · ¥299
- 显示器支架 · ¥359
当前选择
尚未选择商品
点击列表中的按钮选择商品。
更新购物车不会改变子组件的 props,Vue 会跳过不必要的子组件更新。
API 对照
| React | Vue 3 |
|---|---|
useCallback(fn, deps) | 通常直接声明普通函数 |
| 每次 render 都重新执行组件函数 | setup 每个实例通常只执行一次 |
| 手动维护闭包依赖 | 通过 ref 读取最新响应式值 |
常与 memo 子组件配合 | props 未变化时框架可跳过子组件更新 |
useCallback(fn, deps) 可理解为 useMemo(() => fn, deps)。只有回调传给 memo 子组件,或作为其他 Hook 的依赖时,稳定引用通常才有价值。