[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"knowledge-\u002Fapi\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fhooks\u002Fcontext":3,"knowledge-related-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fhooks\u002Fcontext":769,"knowledge-series-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fhooks\u002Fcontext":795},{"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":765,"stem":766,"id":767,"extension":768},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fhooks\u002Fcontext","Context","Context 在组件树中跨层级传递数据，避免 props 逐层透传。涵盖创建与使用、自定义 Hook 封装、性能拆分、与全局状态库对照、最佳实践与常见坑。","技能小册",[12,13,14],"react","hooks","context","2021-10-16",false,{"type":18,"value":19,"toc":753},"minimark",[20,23,43,47,63,66,195,245,249,252,370,388,392,441,451,455,517,522,526,548,551,586,589,660,663,682,685,688,749],[21,22,8],"h1",{"id":14},[24,25,26,27,31,32,37,38,42],"p",{},"Context 在组件树中",[28,29,30],"strong",{},"跨层级","传递数据，避免 props 逐层透传。见 ",[33,34,36],"a",{"href":35},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fcomponents","组件","、",[33,39,41],{"href":40},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fstate","状态管理","。",[44,45,46],"h2",{"id":46},"核心概念",[24,48,49,50,53,54,58,59,62],{},"没有 Context 时，跨越多层组件传递数据只能靠",[28,51,52],{},"逐层透传 props","（\"prop drilling\"）——中间的每一层组件即使自己不需要这份数据，也得帮忙往下传。Context 提供了一种「广播」机制：",[55,56,57],"code",{},"Provider"," 在树的某一层设置一个值，其下任意深度的组件都能用 ",[55,60,61],{},"useContext"," 直接读取，跳过中间层。",[44,64,65],{"id":65},"创建与使用",[67,68,73],"pre",{"className":69,"code":70,"language":71,"meta":72,"style":72},"language-tsx shiki shiki-themes material-theme-lighter github-light github-dark","import { createContext, useContext, useState, type ReactNode } from \"react\";\n\ntype Theme = \"light\" | \"dark\";\n\u002F\u002F 默认值传 undefined（而非真实的 \"light\"），才能在 useTheme 中检测「未包裹 Provider」\nconst ThemeContext = createContext\u003CTheme | undefined>(undefined);\n\nexport function ThemeProvider({ children }: { children: ReactNode }) {\n  const [theme, setTheme] = useState\u003CTheme>(\"light\");\n  return (\n    \u003CThemeContext.Provider value={theme}>{children}\u003C\u002FThemeContext.Provider>\n  );\n}\n\nexport function useTheme() {\n  const theme = useContext(ThemeContext);\n  if (theme === undefined) {\n    throw new Error(\"useTheme 须在 ThemeProvider 内使用\");\n  }\n  return theme;\n}\n","tsx","",[55,74,75,83,90,96,102,108,113,119,125,131,137,143,149,154,160,166,172,178,184,190],{"__ignoreMap":72},[76,77,80],"span",{"class":78,"line":79},"line",1,[76,81,82],{},"import { createContext, useContext, useState, type ReactNode } from \"react\";\n",[76,84,86],{"class":78,"line":85},2,[76,87,89],{"emptyLinePlaceholder":88},true,"\n",[76,91,93],{"class":78,"line":92},3,[76,94,95],{},"type Theme = \"light\" | \"dark\";\n",[76,97,99],{"class":78,"line":98},4,[76,100,101],{},"\u002F\u002F 默认值传 undefined（而非真实的 \"light\"），才能在 useTheme 中检测「未包裹 Provider」\n",[76,103,105],{"class":78,"line":104},5,[76,106,107],{},"const ThemeContext = createContext\u003CTheme | undefined>(undefined);\n",[76,109,111],{"class":78,"line":110},6,[76,112,89],{"emptyLinePlaceholder":88},[76,114,116],{"class":78,"line":115},7,[76,117,118],{},"export function ThemeProvider({ children }: { children: ReactNode }) {\n",[76,120,122],{"class":78,"line":121},8,[76,123,124],{},"  const [theme, setTheme] = useState\u003CTheme>(\"light\");\n",[76,126,128],{"class":78,"line":127},9,[76,129,130],{},"  return (\n",[76,132,134],{"class":78,"line":133},10,[76,135,136],{},"    \u003CThemeContext.Provider value={theme}>{children}\u003C\u002FThemeContext.Provider>\n",[76,138,140],{"class":78,"line":139},11,[76,141,142],{},"  );\n",[76,144,146],{"class":78,"line":145},12,[76,147,148],{},"}\n",[76,150,152],{"class":78,"line":151},13,[76,153,89],{"emptyLinePlaceholder":88},[76,155,157],{"class":78,"line":156},14,[76,158,159],{},"export function useTheme() {\n",[76,161,163],{"class":78,"line":162},15,[76,164,165],{},"  const theme = useContext(ThemeContext);\n",[76,167,169],{"class":78,"line":168},16,[76,170,171],{},"  if (theme === undefined) {\n",[76,173,175],{"class":78,"line":174},17,[76,176,177],{},"    throw new Error(\"useTheme 须在 ThemeProvider 内使用\");\n",[76,179,181],{"class":78,"line":180},18,[76,182,183],{},"  }\n",[76,185,187],{"class":78,"line":186},19,[76,188,189],{},"  return theme;\n",[76,191,193],{"class":78,"line":192},20,[76,194,148],{},[196,197,198,211],"table",{},[199,200,201],"thead",{},[202,203,204,208],"tr",{},[205,206,207],"th",{},"实践",[205,209,210],{},"说明",[212,213,214,229,237],"tbody",{},[202,215,216,223],{},[217,218,219,220],"td",{},"导出 ",[28,221,222],{},"自定义 Hook",[217,224,225,226,228],{},"封装 ",[55,227,61],{},"，统一校验与类型",[202,230,231,234],{},[217,232,233],{},"Provider 靠近根节点",[217,235,236],{},"仅包裹需要该数据的子树",[202,238,239,242],{},[217,240,241],{},"拆分 Context",[217,243,244],{},"频繁变化的数据与稳定配置分 Context，减少无关重渲染",[44,246,248],{"id":247},"完整示例带-setter-的-context","完整示例：带 setter 的 Context",[24,250,251],{},"多数实际场景中 Context 不仅传值，还要传更新方法：",[67,253,255],{"className":69,"code":254,"language":71,"meta":72,"style":72},"interface ThemeContextValue {\n  theme: Theme;\n  toggleTheme: () => void;\n}\n\nconst ThemeContext = createContext\u003CThemeContextValue | undefined>(undefined);\n\nexport function ThemeProvider({ children }: { children: ReactNode }) {\n  const [theme, setTheme] = useState\u003CTheme>(\"light\");\n  const toggleTheme = useCallback(() => {\n    setTheme((t) => (t === \"light\" ? \"dark\" : \"light\"));\n  }, []);\n\n  \u002F\u002F 用 useMemo 稳定 value 引用，避免每次 Provider 渲染都创建新对象\n  const value = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]);\n\n  return \u003CThemeContext.Provider value={value}>{children}\u003C\u002FThemeContext.Provider>;\n}\n\nexport function useTheme() {\n  const ctx = useContext(ThemeContext);\n  if (!ctx) throw new Error(\"useTheme 须在 ThemeProvider 内使用\");\n  return ctx;\n}\n",[55,256,257,262,267,272,276,280,285,289,293,297,302,307,312,316,321,326,330,335,339,343,347,353,359,365],{"__ignoreMap":72},[76,258,259],{"class":78,"line":79},[76,260,261],{},"interface ThemeContextValue {\n",[76,263,264],{"class":78,"line":85},[76,265,266],{},"  theme: Theme;\n",[76,268,269],{"class":78,"line":92},[76,270,271],{},"  toggleTheme: () => void;\n",[76,273,274],{"class":78,"line":98},[76,275,148],{},[76,277,278],{"class":78,"line":104},[76,279,89],{"emptyLinePlaceholder":88},[76,281,282],{"class":78,"line":110},[76,283,284],{},"const ThemeContext = createContext\u003CThemeContextValue | undefined>(undefined);\n",[76,286,287],{"class":78,"line":115},[76,288,89],{"emptyLinePlaceholder":88},[76,290,291],{"class":78,"line":121},[76,292,118],{},[76,294,295],{"class":78,"line":127},[76,296,124],{},[76,298,299],{"class":78,"line":133},[76,300,301],{},"  const toggleTheme = useCallback(() => {\n",[76,303,304],{"class":78,"line":139},[76,305,306],{},"    setTheme((t) => (t === \"light\" ? \"dark\" : \"light\"));\n",[76,308,309],{"class":78,"line":145},[76,310,311],{},"  }, []);\n",[76,313,314],{"class":78,"line":151},[76,315,89],{"emptyLinePlaceholder":88},[76,317,318],{"class":78,"line":156},[76,319,320],{},"  \u002F\u002F 用 useMemo 稳定 value 引用，避免每次 Provider 渲染都创建新对象\n",[76,322,323],{"class":78,"line":162},[76,324,325],{},"  const value = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]);\n",[76,327,328],{"class":78,"line":168},[76,329,89],{"emptyLinePlaceholder":88},[76,331,332],{"class":78,"line":174},[76,333,334],{},"  return \u003CThemeContext.Provider value={value}>{children}\u003C\u002FThemeContext.Provider>;\n",[76,336,337],{"class":78,"line":180},[76,338,148],{},[76,340,341],{"class":78,"line":186},[76,342,89],{"emptyLinePlaceholder":88},[76,344,345],{"class":78,"line":192},[76,346,159],{},[76,348,350],{"class":78,"line":349},21,[76,351,352],{},"  const ctx = useContext(ThemeContext);\n",[76,354,356],{"class":78,"line":355},22,[76,357,358],{},"  if (!ctx) throw new Error(\"useTheme 须在 ThemeProvider 内使用\");\n",[76,360,362],{"class":78,"line":361},23,[76,363,364],{},"  return ctx;\n",[76,366,368],{"class":78,"line":367},24,[76,369,148],{},[24,371,372,375,376,379,380,383,384,387],{},[28,373,374],{},"关键点","：",[55,377,378],{},"value"," 对象若每次渲染都重新创建（未 ",[55,381,382],{},"useMemo","），所有消费该 Context 的组件都会认为「值变了」而重渲染，即使 ",[55,385,386],{},"theme"," 实际没变。",[44,389,391],{"id":390},"拆分-context-提升性能","拆分 Context 提升性能",[67,393,395],{"className":69,"code":394,"language":71,"meta":72,"style":72},"\u002F\u002F 不佳：所有数据塞进一个 Context，任何字段变化都导致全部消费者重渲染\nconst AppContext = createContext\u003C\n  { user: User; theme: Theme; notifications: Notification[] } | undefined\n>(undefined); \u002F\u002F createContext 必须传默认值参数\n\n\u002F\u002F 更好：按更新频率拆分\nconst UserContext = createContext\u003CUser | undefined>(undefined);   \u002F\u002F 低频\nconst ThemeContext = createContext\u003CTheme>(\"light\");                 \u002F\u002F 低频\nconst NotificationsContext = createContext\u003CNotification[]>([]);     \u002F\u002F 高频\n",[55,396,397,402,407,412,417,421,426,431,436],{"__ignoreMap":72},[76,398,399],{"class":78,"line":79},[76,400,401],{},"\u002F\u002F 不佳：所有数据塞进一个 Context，任何字段变化都导致全部消费者重渲染\n",[76,403,404],{"class":78,"line":85},[76,405,406],{},"const AppContext = createContext\u003C\n",[76,408,409],{"class":78,"line":92},[76,410,411],{},"  { user: User; theme: Theme; notifications: Notification[] } | undefined\n",[76,413,414],{"class":78,"line":98},[76,415,416],{},">(undefined); \u002F\u002F createContext 必须传默认值参数\n",[76,418,419],{"class":78,"line":104},[76,420,89],{"emptyLinePlaceholder":88},[76,422,423],{"class":78,"line":110},[76,424,425],{},"\u002F\u002F 更好：按更新频率拆分\n",[76,427,428],{"class":78,"line":115},[76,429,430],{},"const UserContext = createContext\u003CUser | undefined>(undefined);   \u002F\u002F 低频\n",[76,432,433],{"class":78,"line":121},[76,434,435],{},"const ThemeContext = createContext\u003CTheme>(\"light\");                 \u002F\u002F 低频\n",[76,437,438],{"class":78,"line":127},[76,439,440],{},"const NotificationsContext = createContext\u003CNotification[]>([]);     \u002F\u002F 高频\n",[24,442,443,444,447,448,450],{},"只订阅 ",[55,445,446],{},"NotificationsContext"," 的组件不会因为 ",[55,449,386],{}," 切换而重渲染，反之亦然。",[44,452,454],{"id":453},"context-与全局状态库","Context 与全局状态库",[196,456,457,468],{},[199,458,459],{},[202,460,461,463,465],{},[205,462],{},[205,464,8],{},[205,466,467],{},"Redux \u002F Zustand 等",[212,469,470,481,495,506],{},[202,471,472,475,478],{},[217,473,474],{},"内置",[217,476,477],{},"是",[217,479,480],{},"需安装",[202,482,483,486,492],{},[217,484,485],{},"适用",[217,487,488,489],{},"主题、语言、鉴权用户信息等",[28,490,491],{},"低频更新",[217,493,494],{},"复杂全局状态、时间旅行、中间件",[202,496,497,500,503],{},[217,498,499],{},"性能",[217,501,502],{},"消费方在 value 变化时重渲染",[217,504,505],{},"库通常有更细粒度订阅",[202,507,508,511,514],{},[217,509,510],{},"更新粒度",[217,512,513],{},"无法只订阅某个字段（除非手动拆多个 Context）",[217,515,516],{},"支持 selector，只订阅用到的切片",[24,518,519,520,42],{},"全局复杂状态见 ",[33,521,41],{"href":40},[44,523,525],{"id":524},"何时不该用-context","何时不该用 Context",[527,528,529,536,542],"ul",{},[530,531,532,535],"li",{},[28,533,534],{},"高频更新的数据","（如鼠标位置、每秒多次的实时数据）：Context 变化会让所有消费者重渲染，应改用状态库的 selector 机制或专门的发布订阅方案。",[530,537,538,541],{},[28,539,540],{},"只有一两层的 props 传递","：直接传 props 更直观，Context 会增加一层间接性，非必要不用。",[530,543,544,547],{},[28,545,546],{},"组件库内部的局部状态共享","（如 Tabs 组件内 Tab 与 TabPanel 的联动）：Context 很适合，但要注意把它做成组件私有实现细节，不对外暴露。",[44,549,550],{"id":550},"最佳实践",[527,552,553,564,573,576,583],{},[530,554,555,556,559,560,563],{},"始终封装自定义 Hook（",[55,557,558],{},"useTheme","）而不是让消费方直接 ",[55,561,562],{},"useContext(ThemeContext)","，方便统一做空值校验与未来重构。",[530,565,566,567,569,570,572],{},"Provider 传递的 ",[55,568,378],{}," 用 ",[55,571,382],{}," 稳定引用，避免无意义的重渲染。",[530,574,575],{},"按数据的更新频率拆分多个 Context，而不是一个「大而全」的 AppContext。",[530,577,578,579,582],{},"Context 默认值应该有意义（如 ",[55,580,581],{},"light"," 主题），或抛出清晰的错误提示未在 Provider 内使用。",[530,584,585],{},"组件库\u002F复用组件内部可以用 Context 传递跨组件状态（如表单字段间的联动），但应视为实现细节，避免污染公共 API。",[44,587,588],{"id":588},"常见坑",[196,590,591,604],{},[199,592,593],{},[202,594,595,598,601],{},[205,596,597],{},"现象",[205,599,600],{},"常见原因",[205,602,603],{},"处理",[212,605,606,617,630,649],{},[202,607,608,611,614],{},[217,609,610],{},"消费方全部重渲染，即使只关心一个字段",[217,612,613],{},"单一大 Context，value 是聚合对象",[217,615,616],{},"拆分多个 Context 或改用状态库 selector",[202,618,619,624,627],{},[217,620,621,623],{},[55,622,558],{}," 报错「须在 Provider 内使用」",[217,625,626],{},"组件树中未包裹对应 Provider",[217,628,629],{},"检查 Provider 位置，确保覆盖使用该 Hook 的子树",[202,631,632,635,643],{},[217,633,634],{},"Provider 每次渲染都导致子树重渲染",[217,636,637,639,640,642],{},[55,638,378],{}," 未 ",[55,641,382],{},"，每次都是新对象",[217,644,645,646,648],{},"用 ",[55,647,382],{}," 包裹 value",[202,650,651,654,657],{},[217,652,653],{},"Context 被当作「万能全局状态」滥用",[217,655,656],{},"缺乏对状态库的评估，什么都往 Context 里塞",[217,658,659],{},"复杂\u002F高频状态迁移到专门的状态管理方案",[44,661,662],{"id":662},"延伸阅读",[527,664,665,670,675],{},[530,666,667,669],{},[33,668,41],{"href":40},"：Context 与 Redux\u002FZustand 的取舍",[530,671,672,674],{},[33,673,36],{"href":35},"：组件组合与 children 模式",[530,676,677,681],{},[33,678,680],{"href":679},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fperformance\u002Fruntime","运行时优化","：重渲染成因与优化",[44,683,684],{"id":684},"参考文献",[24,686,687],{},"以下链接在编写时均可正常访问：",[196,689,690,699],{},[199,691,692],{},[202,693,694,697],{},[205,695,696],{},"资料",[205,698,210],{},[212,700,701,714,726,737],{},[202,702,703,711],{},[217,704,705],{},[33,706,710],{"href":707,"rel":708},"https:\u002F\u002Fzh-hans.react.dev\u002Freference\u002Freact\u002FuseContext",[709],"nofollow","React：useContext",[217,712,713],{},"API",[202,715,716,723],{},[217,717,718],{},[33,719,722],{"href":720,"rel":721},"https:\u002F\u002Fzh-hans.react.dev\u002Flearn\u002Fpassing-data-deeply-with-context",[709],"React：使用 Context",[217,724,725],{},"教程",[202,727,728,735],{},[217,729,730],{},[33,731,734],{"href":732,"rel":733},"https:\u002F\u002Fzh-hans.react.dev\u002Freference\u002Freact\u002FcreateContext",[709],"React：createContext",[217,736,713],{},[202,738,739,746],{},[217,740,741],{},[33,742,745],{"href":743,"rel":744},"https:\u002F\u002Fzh-hans.react.dev\u002Flearn\u002Fscaling-up-with-reducer-and-context",[709],"React：Scaling Up with Reducer and Context",[217,747,748],{},"组合模式",[750,751,752],"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);}",{"title":72,"searchDepth":85,"depth":85,"links":754},[755,756,757,758,759,760,761,762,763,764],{"id":46,"depth":85,"text":46},{"id":65,"depth":85,"text":65},{"id":247,"depth":85,"text":248},{"id":390,"depth":85,"text":391},{"id":453,"depth":85,"text":454},{"id":524,"depth":85,"text":525},{"id":550,"depth":85,"text":550},{"id":588,"depth":85,"text":588},{"id":662,"depth":85,"text":662},{"id":684,"depth":85,"text":684},{"title":8,"description":9},"knowledge\u002Fbooklet\u002Fframe\u002FReact\u002Fhooks\u002Fcontext","knowledge\u002Fknowledge\u002Fbooklet\u002Fframe\u002FReact\u002Fhooks\u002Fcontext.md","md",{"items":770},[771,775,779,783,787,791],{"path":772,"title":773,"description":774,"category":10,"score":127},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fhooks\u002Feffect","Effect 与副作用","useEffect 用于在组件渲染后执行与外部系统同步的逻辑（请求、订阅、手动改 DOM 等）。涵盖基本用法、依赖数组、适用\u002F不适用场景、useLayoutEffect、最佳实践与常见坑。",{"path":776,"title":777,"description":778,"category":10,"score":127},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fhooks\u002Fmemo","Memo、Callback 与 Ref","本文介绍 useRef、useMemo、useCallback、memo 及自定义 Hook。见 Effect。",{"path":780,"title":781,"description":782,"category":10,"score":127},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fhooks\u002Fstate","State 与 Reducer","Hooks 让函数组件拥有 state 与副作用能力。本文介绍 useState 与 useReducer。见 React 基础。",{"path":784,"title":785,"description":786,"category":10,"score":98},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Frendering","渲染与协调","React 将组件渲染为虚拟 DOM 树，再协调（reconciliation）到浏览器 DOM。理解该过程有助于解释 key、memo 与性能优化。涵盖渲染流程、Fiber、startTransition、Suspense、最佳实践与常见坑。",{"path":788,"title":789,"description":790,"category":10,"score":98},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fproject","工程化","新建 React 项目时，官方与社区主流工具为 Vite；全栈场景常用 Next.js。涵盖 Vite + React、Create React App 现状、Next.js、目录结构、环境变量、部署、最佳实践与常见坑。",{"path":792,"title":793,"description":794,"category":10,"score":98},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Ftypescript","TypeScript","React 官方推荐 TypeScript。类型主要落在 Props、事件、Ref 与 Hooks 返回值上。见 组件。",{"series":796,"title":13,"items":797,"index":805,"prev":806,"next":807},"booklet\u002Fframe\u002Freact\u002Fhooks",[798,799,801,803],{"path":7,"title":8,"date":15,"pinned":16},{"path":772,"title":773,"date":800,"pinned":16},"2024-10-11",{"path":776,"title":777,"date":802,"pinned":16},"2021-02-28",{"path":780,"title":781,"date":804,"pinned":16},"2021-02-21",0,null,{"path":772,"title":773,"date":800,"pinned":16}]