跳转到内容

useCallback 与 Vue 普通函数

父组件同时维护商品选择和购物车数量。增加购物车只更新父组件中的无关状态,不应该让商品列表进行不必要的更新。

React

useCallback 稳定函数引用,让 memo 子组件跳过无关更新。

在线预览

  • 机械键盘 · ¥499
  • 无线鼠标 · ¥299
  • 显示器支架 · ¥359
当前选择

尚未选择商品

点击列表中的按钮选择商品。

更新购物车会重新渲染父组件,但稳定的 onSelect 不会破坏 memo 缓存。

组件关系与数据流

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:#ffffff,stroke:#149eca,color:#172033,stroke-width:1.2px
  classDef component fill:#e5f6fb,stroke:#149eca,color:#172033,stroke-width:2px,font-weight:600
  classDef process fill:#f4fbfd,stroke:#149eca,color:#172033,stroke-width:1.6px
  classDef resource fill:#f3f5f8,stroke:#778195,color:#172033,stroke-width:1.4px

组件每次 state 更新都会执行;useCallback 稳定函数引用,memo 再比较 props。

React 流程图
React 组件关系与数据流

滚轮缩放 · 按住拖动 · 双击重置 · Esc 关闭

实际运行源码

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

setup 中的普通函数天然稳定,子组件通过 emit 通知父组件。

在线预览

  • 机械键盘 · ¥499
  • 无线鼠标 · ¥299
  • 显示器支架 · ¥359
当前选择

尚未选择商品

点击列表中的按钮选择商品。

更新购物车不会改变子组件的 props,Vue 会跳过不必要的子组件更新。

组件关系与数据流

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:#ffffff,stroke:#42b883,color:#172033,stroke-width:1.2px
  classDef component fill:#e7f8f0,stroke:#42b883,color:#172033,stroke-width:2px,font-weight:600
  classDef process fill:#f2fbf7,stroke:#42b883,color:#172033,stroke-width:1.6px
  classDef resource fill:#f3f5f8,stroke:#778195,color:#172033,stroke-width:1.4px

setup 创建一次普通函数;响应式依赖分别更新父模板和子组件 props。

Vue 3 流程图
Vue 3 组件关系与数据流

滚轮缩放 · 按住拖动 · 双击重置 · Esc 关闭

实际运行源码

<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 Vue
useCallback(fn, [deps]) 通常直接声明 function fn()
函数组件每次渲染都会重新执行 setup 每个组件实例通常只执行一次
手动维护闭包依赖 函数通过 ref.value 读取最新响应式值
常与 memo 子组件配合 props 未变化时,Vue 会跳过不必要的子组件更新

useCallback(fn, deps) 可以理解为 useMemo(() => fn, deps):它缓存的是函数本身,不是函数执行后的结果。