React
useMemo 根据手动依赖缓存筛选、排序和汇总结果。
在线预览
缓存的计算结果
5,000 件商品
合计 ¥2,738,800
- 机械键盘 0001¥99
- 机械键盘 0901¥99
- 机械键盘 1801¥99
- 机械键盘 2701¥99
- 机械键盘 3601¥99
购物车不是依赖,更新它会复用上一次计算结果。
组件关系与数据流
StateComponentCached DerivedData
差异焦点缓存正确性依赖开发者维护完整的依赖数组。
实线表示依赖、数据或命令传递,虚线表示事件、回调或清理路径。
flowchart TB
accTitle: React useMemo 显式依赖缓存
accDescr: ReactDemo 每次状态更新都会重新执行,useMemo 手动比较 keyword 和 sortOrder 依赖数组,只有依赖变化才重新计算 result。
state(["keyword / sortOrder / cartCount"]) --> render["ReactDemo 每次 state 更新都执行"]
render --> deps[("手写 [keyword, sortOrder]")]
products[("5,000 products")] --> memo{{"useMemo 计算或复用 result"}}
deps --> memo
memo --> ui["结果 UI"]
class state state
class render,ui component
class memo process
class deps,products resource
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组件仍会重新执行;useMemo 通过手写依赖数组决定计算还是复用。
实际运行源码
import { useMemo, useState } from "react";
type SortOrder = "asc" | "desc";
const categories = ["机械键盘", "无线鼠标", "显示器", "前端书籍"];
const products = Array.from({ length: 5_000 }, (_, index) => ({ id: index + 1, name: `${categories[index % categories.length]} ${String(index + 1).padStart(4, "0")}`, price: 99 + ((index * 37) % 900),}));
const priceFormatter = new Intl.NumberFormat("zh-CN", { style: "currency", currency: "CNY", maximumFractionDigits: 0,});
export function ReactDemo() { const [keyword, setKeyword] = useState(""); const [sortOrder, setSortOrder] = useState<SortOrder>("asc"); const [cartCount, setCartCount] = useState(0);
const result = useMemo(() => { const visibleProducts = products .filter((product) => product.name.includes(keyword.trim())) .sort((a, b) => sortOrder === "asc" ? a.price - b.price : b.price - a.price, );
return { count: visibleProducts.length, total: visibleProducts.reduce((sum, product) => sum + product.price, 0), preview: visibleProducts.slice(0, 5), }; }, [keyword, sortOrder]);
return ( <div className="demo-stack"> <div className="demo-form"> <label className="demo-field"> 搜索 5,000 条商品 <input className="demo-input" value={keyword} placeholder="例如 机械键盘" onChange={(event) => setKeyword(event.target.value)} /> </label> <label className="demo-field"> 价格排序 <select className="demo-select" value={sortOrder} onChange={(event) => setSortOrder(event.target.value as SortOrder)} > <option value="asc">从低到高</option> <option value="desc">从高到低</option> </select> </label> </div>
<div className="demo-card"> <span className="demo-tag">缓存的计算结果</span> <h2>{result.count.toLocaleString()} 件商品</h2> <p>合计 {priceFormatter.format(result.total)}</p> </div>
<ul className="demo-list"> {result.preview.map((product) => ( <li key={product.id}> <span>{product.name}</span> <span>{priceFormatter.format(product.price)}</span> </li> ))} </ul>
<div className="demo-actions"> <button className="demo-button" type="button" onClick={() => setCartCount((count) => count + 1)} > 购物车 +1(当前 {cartCount}) </button> </div> <p className="demo-muted">购物车不是依赖,更新它会复用上一次计算结果。</p> </div> );}