你也有这种经历吧:
在 App
里拿到的用户信息,只想喂给最深层的 Avatar
用,结果一路上「父子父子」传了五六层,改个字段名都要全局搜索替换。改完测试一跑,某个中间件漏了,页面白屏------这就是传说中的「数据沉钻 / prop drilling」。
我之前干过的蠢事:
- 从
App
→Header
→Nav
→UserMenu
→Avatar
一路手动把user={user}
传到底 - 后来想把
user
改名叫currentUser
,漏改了一个文件,生产环境直接挂 - 想重构组件结构,发现每个中间层都得加、改、删 props,根本动弹不得
现在我用 Provider Pattern,三步搞定。
我现在快速上手的 3 个步骤
-
建立 Context
在项目根目录建
context.js
:export const DataContext = React.createContext()
如果你想给「主题」「用户信息」「语言包」都建 Context,重复这行就行,别吝啬
-
把
Provider
当壳子在根组件用
DataContext.Provider
包一层js// App.jsx import { DataContext } from './context'; function App() { const data = { listItem: "一条数据", title: "标题", text: "正文" }; return ( <DataContext.Provider value={data}> <SideBar /> <Content /> </DataContext.Provider> ); }
只有真正需要 data 的子组件才会吃到,中间任何层都不用再管
data
-
底层组件直接
useContext
吃值jsimport { useContext } from 'react'; import { DataContext } from './context'; function ListItem() { const data = useContext(DataContext); return <span>{data.listItem}</span>; }
React 会自动从最近的 Provider 拿值,不需要任何 props
就这么简单。你再也不用在层级间「过手」无关数据,未来改字段名只在 Context 和用的地方各改一处。
实战:主题切换 5 分钟搞定
-
先造主题色表
const themes = { light: { bg: '#fff', color: '#000' }, dark: { bg: '#171717', color: '#fff' } }
-
造一个主题 Context 和对应的 Provider
jsimport { createContext, useContext, useState } from 'react'; export const ThemeCtx = createContext(); export function ThemeProvider({ children }) { const [theme, setTheme] = useState('dark'); const toggle = () => setTheme(t => (t === 'light' ? 'dark' : 'light')); const value = { theme: themes[theme], toggle }; return <ThemeCtx.Provider value={value}>{children}</ThemeCtx.Provider>; }
-
让 App 直接包一层 Provider
jsx<ThemeProvider> <Toggle /> <List /> </ThemeProvider>
-
Toggle 组件里一键切换
jsimport { useContext } from 'react'; import { ThemeCtx } from './ThemeProvider'; export default function Toggle() { const { toggle } = useContext(ThemeCtx); return <input type="checkbox" onClick={toggle} />; }
-
ListItem 拿到主题色自动变风格
jsexport default function ListItem() { const { theme } = useContext(ThemeCtx); return <li style={{ backgroundColor: theme.bg, color: theme.color }}>内容</li>; }
所有中间组件(Toggle、List、List 内部的任何 wrapper)都不用管主题这件事
再进阶:自定义 Hook + 错误兜底(别再写重复代码)
我之前每个组件都写两样板行:useContext(SomeCtx)
,还要复制判断 if (!ctx) throw Error
现在我打包成一个「定制 Hook」:
js
function useTheme() {
const ctx = useContext(ThemeCtx);
if (!ctx) throw new Error('useTheme 必须在 ThemeProvider 里用');
return ctx;
}
以后任何组件直接 const { theme, toggle } = useTheme();
就行,少两行,还附带强制提示,用错直接报错,调试飞快。
小心:不要滥用 Provider
Provider 虽然爽,但所有消费组件会在 Provider 的 value
改变时全部重新渲染。我曾经把计数器和静态文案放在同一个 Context 里,结果每次点击「+1」连「版权声明」这类死文字都闪一下,性能崩掉。
我现在遵循的做法:高频更新的状态单独一个 Provider,静态全局配置另起炉灶,互不干扰。
总结一句话
先判断「这是不是会被很多层组件一起用到的全局值」,如果是,立刻套 Provider + useContext;写错地方会 throw Error,重构时只需改一处,爽到飞。