[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"knowledge-\u002Fapi\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fperformance\u002Fruntime":3,"knowledge-related-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fperformance\u002Fruntime":1039,"knowledge-series-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fperformance\u002Fruntime":1059},{"source":4,"connector":5,"page":6},"sqlite","better-sqlite3",{"path":7,"title":8,"description":9,"category":10,"tags":11,"date":15,"pinned":16,"draft":16,"body":17,"seo":1035,"stem":1036,"id":1037,"extension":1038},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fperformance\u002Fruntime","运行时优化","减少不必要的组件重渲染与昂贵计算。涵盖重渲染成因、memo\u002FuseMemo\u002FuseCallback、虚拟滚动、Web Worker、Profiler 分析、最佳实践与常见坑。构建侧见 build。","技能小册",[12,13,14],"react","performance","runtime","2020-02-05",false,{"type":18,"value":19,"toc":1021},"minimark",[20,23,33,37,50,53,122,126,284,301,311,425,437,440,447,464,588,591,594,616,718,772,775,786,789,828,831,919,922,941,944,947,1017],[21,22,8],"h1",{"id":8},[24,25,26,27,32],"p",{},"减少不必要的组件重渲染与昂贵计算。构建侧见 ",[28,29,31],"a",{"href":30},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fperformance\u002Fbuild","build","。",[34,35,36],"h2",{"id":36},"核心概念",[24,38,39,40,44,45,49],{},"React 的重渲染规则很简单：",[41,42,43],"strong",{},"父组件重渲染，默认所有子组件都会重渲染","（不管 props 是否变化），除非子组件被 ",[46,47,48],"code",{},"memo"," 包裹且 props 浅比较相等。性能优化的本质是在这个默认行为上做「减法」：减少触发渲染的次数、减少每次渲染需要重新计算的内容、减少需要渲染的 DOM 节点数。",[34,51,52],{"id":52},"避免不必要的重渲染",[54,55,56,69],"table",{},[57,58,59],"thead",{},[60,61,62,66],"tr",{},[63,64,65],"th",{},"手段",[63,67,68],{},"说明",[70,71,72,81,89,103,114],"tbody",{},[60,73,74,78],{},[75,76,77],"td",{},"状态下沉 \u002F 提升",[75,79,80],{},"把 state 放在真正需要的子树根部",[60,82,83,86],{},[75,84,85],{},"Context 拆分",[75,87,88],{},"高频与低频数据分 Context",[60,90,91,96],{},[75,92,93,95],{},[46,94,48],{}," + 稳定 props",[75,97,98,99],{},"见 ",[28,100,102],{"href":101},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fhooks\u002Fmemo","memo Hook",[60,104,105,108],{},[75,106,107],{},"选择器订阅",[75,109,110,113],{},[46,111,112],{},"react-redux"," \u002F Zustand 细粒度选取 state",[60,115,116,119],{},[75,117,118],{},"children 作为组件而非渲染函数",[75,120,121],{},"避免每次渲染都创建新的内联组件定义",[34,123,125],{"id":124},"示例状态下沉","示例：状态下沉",[127,128,133],"pre",{"className":129,"code":130,"language":131,"meta":132,"style":132},"language-tsx shiki shiki-themes material-theme-lighter github-light github-dark","\u002F\u002F 不佳：count 变化会连带整个 Page 重渲染，包括不相关的 Header\u002FFooter\nfunction Page() {\n  const [count, setCount] = useState(0);\n  return (\n    \u003Cdiv>\n      \u003CHeader \u002F>\n      \u003Cbutton onClick={() => setCount((c) => c + 1)}>{count}\u003C\u002Fbutton>\n      \u003CFooter \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n\n\u002F\u002F 更好：把 count 状态与它的 UI 收敛到独立组件\nfunction Page() {\n  return (\n    \u003Cdiv>\n      \u003CHeader \u002F>\n      \u003CCounter \u002F>\n      \u003CFooter \u002F>\n    \u003C\u002Fdiv>\n  );\n}\nfunction Counter() {\n  const [count, setCount] = useState(0);\n  return \u003Cbutton onClick={() => setCount((c) => c + 1)}>{count}\u003C\u002Fbutton>;\n}\n","tsx","",[46,134,135,143,149,155,161,167,173,179,185,191,197,203,210,216,221,226,231,236,242,247,252,257,262,268,273,279],{"__ignoreMap":132},[136,137,140],"span",{"class":138,"line":139},"line",1,[136,141,142],{},"\u002F\u002F 不佳：count 变化会连带整个 Page 重渲染，包括不相关的 Header\u002FFooter\n",[136,144,146],{"class":138,"line":145},2,[136,147,148],{},"function Page() {\n",[136,150,152],{"class":138,"line":151},3,[136,153,154],{},"  const [count, setCount] = useState(0);\n",[136,156,158],{"class":138,"line":157},4,[136,159,160],{},"  return (\n",[136,162,164],{"class":138,"line":163},5,[136,165,166],{},"    \u003Cdiv>\n",[136,168,170],{"class":138,"line":169},6,[136,171,172],{},"      \u003CHeader \u002F>\n",[136,174,176],{"class":138,"line":175},7,[136,177,178],{},"      \u003Cbutton onClick={() => setCount((c) => c + 1)}>{count}\u003C\u002Fbutton>\n",[136,180,182],{"class":138,"line":181},8,[136,183,184],{},"      \u003CFooter \u002F>\n",[136,186,188],{"class":138,"line":187},9,[136,189,190],{},"    \u003C\u002Fdiv>\n",[136,192,194],{"class":138,"line":193},10,[136,195,196],{},"  );\n",[136,198,200],{"class":138,"line":199},11,[136,201,202],{},"}\n",[136,204,206],{"class":138,"line":205},12,[136,207,209],{"emptyLinePlaceholder":208},true,"\n",[136,211,213],{"class":138,"line":212},13,[136,214,215],{},"\u002F\u002F 更好：把 count 状态与它的 UI 收敛到独立组件\n",[136,217,219],{"class":138,"line":218},14,[136,220,148],{},[136,222,224],{"class":138,"line":223},15,[136,225,160],{},[136,227,229],{"class":138,"line":228},16,[136,230,166],{},[136,232,234],{"class":138,"line":233},17,[136,235,172],{},[136,237,239],{"class":138,"line":238},18,[136,240,241],{},"      \u003CCounter \u002F>\n",[136,243,245],{"class":138,"line":244},19,[136,246,184],{},[136,248,250],{"class":138,"line":249},20,[136,251,190],{},[136,253,255],{"class":138,"line":254},21,[136,256,196],{},[136,258,260],{"class":138,"line":259},22,[136,261,202],{},[136,263,265],{"class":138,"line":264},23,[136,266,267],{},"function Counter() {\n",[136,269,271],{"class":138,"line":270},24,[136,272,154],{},[136,274,276],{"class":138,"line":275},25,[136,277,278],{},"  return \u003Cbutton onClick={() => setCount((c) => c + 1)}>{count}\u003C\u002Fbutton>;\n",[136,280,282],{"class":138,"line":281},26,[136,283,202],{},[24,285,286,289,290,293,294,297,298,300],{},[46,287,288],{},"Header","、",[46,291,292],{},"Footer"," 不再因为 ",[46,295,296],{},"count"," 变化而重渲染——它们甚至不知道 ",[46,299,296],{}," 的存在。",[34,302,304,305,308,309],{"id":303},"示例usecallback-配合-memo","示例：",[46,306,307],{},"useCallback"," 配合 ",[46,310,48],{},[127,312,314],{"className":129,"code":313,"language":131,"meta":132,"style":132},"const ExpensiveRow = memo(function ExpensiveRow({\n  item,\n  onSelect,\n}: { item: Item; onSelect: (id: string) => void }) {\n  console.log(\"render row\", item.id);\n  return \u003Cli onClick={() => onSelect(item.id)}>{item.name}\u003C\u002Fli>;\n});\n\nfunction List({ items }: { items: Item[] }) {\n  const [selectedId, setSelectedId] = useState\u003Cstring | null>(null);\n\n  \u002F\u002F 若不用 useCallback，每次 List 渲染都会创建新函数引用，\n  \u002F\u002F 使 memo 的浅比较失效，ExpensiveRow 依然会重渲染\n  const handleSelect = useCallback((id: string) => setSelectedId(id), []);\n\n  return (\n    \u003Cul>\n      {items.map((item) => (\n        \u003CExpensiveRow key={item.id} item={item} onSelect={handleSelect} \u002F>\n      ))}\n    \u003C\u002Ful>\n  );\n}\n",[46,315,316,321,326,331,336,341,346,351,355,360,365,369,374,379,384,388,392,397,402,407,412,417,421],{"__ignoreMap":132},[136,317,318],{"class":138,"line":139},[136,319,320],{},"const ExpensiveRow = memo(function ExpensiveRow({\n",[136,322,323],{"class":138,"line":145},[136,324,325],{},"  item,\n",[136,327,328],{"class":138,"line":151},[136,329,330],{},"  onSelect,\n",[136,332,333],{"class":138,"line":157},[136,334,335],{},"}: { item: Item; onSelect: (id: string) => void }) {\n",[136,337,338],{"class":138,"line":163},[136,339,340],{},"  console.log(\"render row\", item.id);\n",[136,342,343],{"class":138,"line":169},[136,344,345],{},"  return \u003Cli onClick={() => onSelect(item.id)}>{item.name}\u003C\u002Fli>;\n",[136,347,348],{"class":138,"line":175},[136,349,350],{},"});\n",[136,352,353],{"class":138,"line":181},[136,354,209],{"emptyLinePlaceholder":208},[136,356,357],{"class":138,"line":187},[136,358,359],{},"function List({ items }: { items: Item[] }) {\n",[136,361,362],{"class":138,"line":193},[136,363,364],{},"  const [selectedId, setSelectedId] = useState\u003Cstring | null>(null);\n",[136,366,367],{"class":138,"line":199},[136,368,209],{"emptyLinePlaceholder":208},[136,370,371],{"class":138,"line":205},[136,372,373],{},"  \u002F\u002F 若不用 useCallback，每次 List 渲染都会创建新函数引用，\n",[136,375,376],{"class":138,"line":212},[136,377,378],{},"  \u002F\u002F 使 memo 的浅比较失效，ExpensiveRow 依然会重渲染\n",[136,380,381],{"class":138,"line":218},[136,382,383],{},"  const handleSelect = useCallback((id: string) => setSelectedId(id), []);\n",[136,385,386],{"class":138,"line":223},[136,387,209],{"emptyLinePlaceholder":208},[136,389,390],{"class":138,"line":228},[136,391,160],{},[136,393,394],{"class":138,"line":233},[136,395,396],{},"    \u003Cul>\n",[136,398,399],{"class":138,"line":238},[136,400,401],{},"      {items.map((item) => (\n",[136,403,404],{"class":138,"line":244},[136,405,406],{},"        \u003CExpensiveRow key={item.id} item={item} onSelect={handleSelect} \u002F>\n",[136,408,409],{"class":138,"line":249},[136,410,411],{},"      ))}\n",[136,413,414],{"class":138,"line":254},[136,415,416],{},"    \u003C\u002Ful>\n",[136,418,419],{"class":138,"line":259},[136,420,196],{},[136,422,423],{"class":138,"line":264},[136,424,202],{},[24,426,427,429,430,433,434,436],{},[46,428,307],{},"\u002F",[46,431,432],{},"useMemo"," 本身不是「万能加速器」，只有配合 ",[46,435,48],{}," 让下游组件真正跳过渲染时才有意义；滥用反而增加代码复杂度和一点点内存\u002F比较开销。",[34,438,439],{"id":439},"列表与虚拟滚动",[24,441,442,443,446],{},"长列表（数千行）应使用",[41,444,445],{},"虚拟滚动","（只渲染可视区域 DOM），例如：",[448,449,450,459],"ul",{},[451,452,453],"li",{},[28,454,458],{"href":455,"rel":456},"https:\u002F\u002Ftanstack.com\u002Fvirtual",[457],"nofollow","TanStack Virtual",[451,460,461],{},[46,462,463],{},"react-window",[127,465,467],{"className":129,"code":466,"language":131,"meta":132,"style":132},"import { useVirtualizer } from \"@tanstack\u002Freact-virtual\";\n\nfunction VirtualList({ items }: { items: Item[] }) {\n  const parentRef = useRef\u003CHTMLDivElement>(null);\n  const virtualizer = useVirtualizer({\n    count: items.length,\n    getScrollElement: () => parentRef.current,\n    estimateSize: () => 48,\n  });\n\n  return (\n    \u003Cdiv ref={parentRef} style={{ height: 480, overflow: \"auto\" }}>\n      \u003Cdiv style={{ height: virtualizer.getTotalSize(), position: \"relative\" }}>\n        {virtualizer.getVirtualItems().map((row) => (\n          \u003Cdiv\n            key={row.key}\n            style={{ position: \"absolute\", top: row.start, height: row.size, width: \"100%\" }}\n          >\n            {items[row.index].name}\n          \u003C\u002Fdiv>\n        ))}\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  );\n}\n",[46,468,469,474,478,483,488,493,498,503,508,513,517,521,526,531,536,541,546,551,556,561,566,571,576,580,584],{"__ignoreMap":132},[136,470,471],{"class":138,"line":139},[136,472,473],{},"import { useVirtualizer } from \"@tanstack\u002Freact-virtual\";\n",[136,475,476],{"class":138,"line":145},[136,477,209],{"emptyLinePlaceholder":208},[136,479,480],{"class":138,"line":151},[136,481,482],{},"function VirtualList({ items }: { items: Item[] }) {\n",[136,484,485],{"class":138,"line":157},[136,486,487],{},"  const parentRef = useRef\u003CHTMLDivElement>(null);\n",[136,489,490],{"class":138,"line":163},[136,491,492],{},"  const virtualizer = useVirtualizer({\n",[136,494,495],{"class":138,"line":169},[136,496,497],{},"    count: items.length,\n",[136,499,500],{"class":138,"line":175},[136,501,502],{},"    getScrollElement: () => parentRef.current,\n",[136,504,505],{"class":138,"line":181},[136,506,507],{},"    estimateSize: () => 48,\n",[136,509,510],{"class":138,"line":187},[136,511,512],{},"  });\n",[136,514,515],{"class":138,"line":193},[136,516,209],{"emptyLinePlaceholder":208},[136,518,519],{"class":138,"line":199},[136,520,160],{},[136,522,523],{"class":138,"line":205},[136,524,525],{},"    \u003Cdiv ref={parentRef} style={{ height: 480, overflow: \"auto\" }}>\n",[136,527,528],{"class":138,"line":212},[136,529,530],{},"      \u003Cdiv style={{ height: virtualizer.getTotalSize(), position: \"relative\" }}>\n",[136,532,533],{"class":138,"line":218},[136,534,535],{},"        {virtualizer.getVirtualItems().map((row) => (\n",[136,537,538],{"class":138,"line":223},[136,539,540],{},"          \u003Cdiv\n",[136,542,543],{"class":138,"line":228},[136,544,545],{},"            key={row.key}\n",[136,547,548],{"class":138,"line":233},[136,549,550],{},"            style={{ position: \"absolute\", top: row.start, height: row.size, width: \"100%\" }}\n",[136,552,553],{"class":138,"line":238},[136,554,555],{},"          >\n",[136,557,558],{"class":138,"line":244},[136,559,560],{},"            {items[row.index].name}\n",[136,562,563],{"class":138,"line":249},[136,564,565],{},"          \u003C\u002Fdiv>\n",[136,567,568],{"class":138,"line":254},[136,569,570],{},"        ))}\n",[136,572,573],{"class":138,"line":259},[136,574,575],{},"      \u003C\u002Fdiv>\n",[136,577,578],{"class":138,"line":264},[136,579,190],{},[136,581,582],{"class":138,"line":270},[136,583,196],{},[136,585,586],{"class":138,"line":275},[136,587,202],{},[24,589,590],{},"避免一次性渲染全部节点；DOM 节点数从「数据总量」降为「可视区域数量」，滚动性能不再随数据量线性下降。",[34,592,593],{"id":593},"昂贵计算",[448,595,596,602,613],{},[451,597,598,599,601],{},"用 ",[46,600,432],{}," 缓存过滤\u002F排序结果。",[451,603,598,604,607,608,612],{},[46,605,606],{},"startTransition"," 降低筛选等更新的优先级（见 ",[28,609,611],{"href":610},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Frendering","渲染","）。",[451,614,615],{},"Web Worker 处理重 CPU 任务（与 React 状态更新分离）：",[127,617,621],{"className":618,"code":619,"language":620,"meta":132,"style":132},"language-ts shiki shiki-themes material-theme-lighter github-light github-dark","\u002F\u002F worker.ts\nself.onmessage = (e) => {\n  const result = heavyCompute(e.data);\n  self.postMessage(result);\n};\n","ts",[46,622,623,629,664,694,713],{"__ignoreMap":132},[136,624,625],{"class":138,"line":139},[136,626,628],{"class":627},"sutJx","\u002F\u002F worker.ts\n",[136,630,631,635,639,643,647,650,654,657,661],{"class":138,"line":145},[136,632,634],{"class":633},"su5hD","self",[136,636,638],{"class":637},"sP7_E",".",[136,640,642],{"class":641},"sGLFI","onmessage",[136,644,646],{"class":645},"smGrS"," =",[136,648,649],{"class":637}," (",[136,651,653],{"class":652},"s99_P","e",[136,655,656],{"class":637},")",[136,658,660],{"class":659},"sbsja"," =>",[136,662,663],{"class":637}," {\n",[136,665,666,669,673,675,678,682,684,686,689,691],{"class":138,"line":151},[136,667,668],{"class":659},"  const",[136,670,672],{"class":671},"s_hVV"," result",[136,674,646],{"class":645},[136,676,677],{"class":641}," heavyCompute",[136,679,681],{"class":680},"skxfh","(",[136,683,653],{"class":633},[136,685,638],{"class":637},[136,687,688],{"class":633},"data",[136,690,656],{"class":680},[136,692,693],{"class":637},";\n",[136,695,696,699,701,704,706,709,711],{"class":138,"line":157},[136,697,698],{"class":633},"  self",[136,700,638],{"class":637},[136,702,703],{"class":641},"postMessage",[136,705,681],{"class":680},[136,707,708],{"class":633},"result",[136,710,656],{"class":680},[136,712,693],{"class":637},[136,714,715],{"class":138,"line":163},[136,716,717],{"class":637},"};\n",[127,719,721],{"className":129,"code":720,"language":131,"meta":132,"style":132},"function useHeavyCompute(input: Input) {\n  const [result, setResult] = useState\u003CResult | null>(null);\n  useEffect(() => {\n    const worker = new Worker(new URL(\".\u002Fworker.ts\", import.meta.url));\n    worker.postMessage(input);\n    worker.onmessage = (e) => setResult(e.data);\n    return () => worker.terminate();\n  }, [input]);\n  return result;\n}\n",[46,722,723,728,733,738,743,748,753,758,763,768],{"__ignoreMap":132},[136,724,725],{"class":138,"line":139},[136,726,727],{},"function useHeavyCompute(input: Input) {\n",[136,729,730],{"class":138,"line":145},[136,731,732],{},"  const [result, setResult] = useState\u003CResult | null>(null);\n",[136,734,735],{"class":138,"line":151},[136,736,737],{},"  useEffect(() => {\n",[136,739,740],{"class":138,"line":157},[136,741,742],{},"    const worker = new Worker(new URL(\".\u002Fworker.ts\", import.meta.url));\n",[136,744,745],{"class":138,"line":163},[136,746,747],{},"    worker.postMessage(input);\n",[136,749,750],{"class":138,"line":169},[136,751,752],{},"    worker.onmessage = (e) => setResult(e.data);\n",[136,754,755],{"class":138,"line":175},[136,756,757],{},"    return () => worker.terminate();\n",[136,759,760],{"class":138,"line":181},[136,761,762],{},"  }, [input]);\n",[136,764,765],{"class":138,"line":187},[136,766,767],{},"  return result;\n",[136,769,770],{"class":138,"line":193},[136,771,202],{},[34,773,774],{"id":774},"开发期分析",[448,776,777,783],{},[451,778,779,782],{},[41,780,781],{},"React DevTools Profiler","：录制交互，查看组件渲染次数与耗时，定位「哪个组件渲染了但其实不需要」。",[451,784,785],{},"结合浏览器 Performance 面板定位长任务（Long Task），尤其是主线程被单次渲染占用超过 50ms 的情况。",[34,787,788],{"id":788},"最佳实践",[448,790,791,804,819,822,825],{},[451,792,793,794,797,798,800,801,803],{},"先用 Profiler ",[41,795,796],{},"测量","再优化，不要凭直觉给所有组件加 ",[46,799,48],{},"（",[46,802,48],{}," 本身有比较开销，对频繁 props 变化的组件反而无益）。",[451,805,806,807,810,811,813,814,429,816,818],{},"内联传给子组件的对象\u002F数组\u002F函数 props（如 ",[46,808,809],{},"style={{ color: 'red' }}","）每次渲染都是新引用，会让 ",[46,812,48],{}," 失效，需要 ",[46,815,432],{},[46,817,307],{}," 或提到组件外部。",[451,820,821],{},"大列表虚拟化优先于「优化每一行渲染速度」——本质问题往往是渲染节点太多，不是节点渲染太慢。",[451,823,824],{},"高频更新（拖拽、动画进度）尽量脱离 React 状态循环，用 ref 直接操作 DOM 或搭配 CSS\u002FWeb Animations API。",[451,826,827],{},"避免过早抽象「性能优化 Hook」，多数中后台系统的瓶颈是网络请求而非渲染，先用 Profiler 确认瓶颈位置。",[34,829,830],{"id":830},"常见坑",[54,832,833,846],{},[57,834,835],{},[60,836,837,840,843],{},[63,838,839],{},"现象",[63,841,842],{},"常见原因",[63,844,845],{},"处理",[70,847,848,867,878,891,902],{},[60,849,850,856,859],{},[75,851,852,853,855],{},"加了 ",[46,854,48],{}," 组件还是每次都重渲染",[75,857,858],{},"传入了每次都新建的对象\u002F函数 props",[75,860,861,862,429,864,866],{},"配合 ",[46,863,432],{},[46,865,307],{}," 稳定引用",[60,868,869,872,875],{},[75,870,871],{},"列表滚动卡顿",[75,873,874],{},"未虚拟化、每行组件较重",[75,876,877],{},"引入 TanStack Virtual \u002F react-window",[60,879,880,883,886],{},[75,881,882],{},"输入框卡顿",[75,884,885],{},"每次按键都触发昂贵的同步计算",[75,887,598,888,890],{},[46,889,606],{}," 降低非紧急更新优先级",[60,892,893,896,899],{},[75,894,895],{},"Context 消费方全部重渲染",[75,897,898],{},"单一大 Context 塞了高频与低频数据",[75,900,901],{},"拆分多个 Context，或引入选择器式状态库",[60,903,904,911,916],{},[75,905,906,429,908,910],{},[46,907,432],{},[46,909,307],{}," 用了却没效果",[75,912,913,914],{},"依赖数组不稳定，或下游组件本身没做 ",[46,915,48],{},[75,917,918],{},"检查依赖数组与子组件是否配合 memo",[34,920,921],{"id":921},"延伸阅读",[448,923,924,930,935],{},[451,925,926,929],{},[28,927,928],{"href":30},"构建与分包","：首屏体积优化",[451,931,932,934],{},[28,933,102],{"href":101},"：组件级缓存原理",[451,936,937,940],{},[28,938,939],{"href":610},"渲染与协调","：Fiber 与 startTransition",[34,942,943],{"id":943},"参考文献",[24,945,946],{},"以下链接在编写时均可正常访问：",[54,948,949,958],{},[57,950,951],{},[60,952,953,956],{},[63,954,955],{},"资料",[63,957,68],{},[70,959,960,972,984,996,1005],{},[60,961,962,969],{},[75,963,964],{},[28,965,968],{"href":966,"rel":967},"https:\u002F\u002Fzh-hans.react.dev\u002Flearn\u002Frender-and-commit#optimizing-performance",[457],"React：性能优化",[75,970,971],{},"官方建议",[60,973,974,981],{},[75,975,976],{},[28,977,980],{"href":978,"rel":979},"https:\u002F\u002Freact.dev\u002Flearn\u002Freact-developer-tools",[457],"React DevTools",[75,982,983],{},"开发者工具",[60,985,986,993],{},[75,987,988],{},[28,989,992],{"href":990,"rel":991},"https:\u002F\u002Fweb.dev\u002Farticles\u002Foptimize-inp",[457],"web.dev：Optimize INP",[75,994,995],{},"交互延迟（英文）",[60,997,998,1003],{},[75,999,1000],{},[28,1001,458],{"href":455,"rel":1002},[457],[75,1004,445],{},[60,1006,1007,1014],{},[75,1008,1009],{},[28,1010,1013],{"href":1011,"rel":1012},"https:\u002F\u002Fzh-hans.react.dev\u002Freference\u002Freact\u002Fmemo",[457],"React：memo",[75,1015,1016],{},"API",[1018,1019,1020],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":132,"searchDepth":145,"depth":145,"links":1022},[1023,1024,1025,1026,1028,1029,1030,1031,1032,1033,1034],{"id":36,"depth":145,"text":36},{"id":52,"depth":145,"text":52},{"id":124,"depth":145,"text":125},{"id":303,"depth":145,"text":1027},"示例：useCallback 配合 memo",{"id":439,"depth":145,"text":439},{"id":593,"depth":145,"text":593},{"id":774,"depth":145,"text":774},{"id":788,"depth":145,"text":788},{"id":830,"depth":145,"text":830},{"id":921,"depth":145,"text":921},{"id":943,"depth":145,"text":943},{"title":8,"description":9},"knowledge\u002Fbooklet\u002Fframe\u002FReact\u002Fperformance\u002Fruntime","knowledge\u002Fknowledge\u002Fbooklet\u002Fframe\u002FReact\u002Fperformance\u002Fruntime.md","md",{"items":1040},[1041,1043,1046,1049,1051,1055],{"path":30,"title":928,"description":1042,"category":10,"score":187},"通过代码分割与构建配置减小首屏 JavaScript 体积。涵盖 React.lazy\u002FSuspense、路由级分割、Tree Shaking、Vite 生产构建、体积分析、最佳实践与常见坑。运行时优化见 runtime。",{"path":1044,"title":8,"description":1045,"category":10,"score":175},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fperformance\u002Fruntime","在 变更检测 策略正确的前提下，减少模板计算与 DOM 规模。涵盖 OnPush、@for track、纯管道、虚拟滚动、懒加载、构建体积分析与常见性能陷阱。",{"path":1047,"title":8,"description":1048,"category":10,"score":175},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fperformance\u002Fruntime","减少不必要的渲染与响应式开销。构建侧见 build。",{"path":610,"title":939,"description":1050,"category":10,"score":157},"React 将组件渲染为虚拟 DOM 树，再协调（reconciliation）到浏览器 DOM。理解该过程有助于解释 key、memo 与性能优化。涵盖渲染流程、Fiber、startTransition、Suspense、最佳实践与常见坑。",{"path":1052,"title":1053,"description":1054,"category":10,"score":157},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fproject","工程化","新建 React 项目时，官方与社区主流工具为 Vite；全栈场景常用 Next.js。涵盖 Vite + React、Create React App 现状、Next.js、目录结构、环境变量、部署、最佳实践与常见坑。",{"path":1056,"title":1057,"description":1058,"category":10,"score":157},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Ftypescript","TypeScript","React 官方推荐 TypeScript。类型主要落在 Props、事件、Ref 与 Hooks 返回值上。见 组件。",{"series":1060,"title":13,"items":1061,"index":139,"prev":1065,"next":1066},"booklet\u002Fframe\u002Freact\u002Fperformance",[1062,1064],{"path":30,"title":928,"date":1063,"pinned":16},"2025-07-23",{"path":7,"title":8,"date":15,"pinned":16},{"path":30,"title":928,"date":1063,"pinned":16},null]