Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Join the discussion
Loading comments...
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
React 如何在组件树中传播值而无需属性透传。
Context API 让你无需在每一层传递 props(属性透传)就能在组件树中共享数据。Provider 组件包裹一个子树并提供一个值。任何后代组件都可以用 useContext 访问该值 — 并在值变化时自动重新渲染。
React.createContext(defaultValue) 创建一个带有 Provider 和 Consumer 的 Context 对象。当组件在树中没有匹配的 Provider 时,使用 defaultValue。
Provider 组件接受一个 value prop。子树中的所有组件都可以访问该值。你可以嵌套同一 context 的多个 Provider — 最近的祖先 Provider 胜出。
useContext(MyContext) 返回调用组件上方最近 Provider 的当前 context 值。它使组件订阅 context 变化。
当 Provider 的 value prop 变化(按引用)时,React 重新渲染子树中所有调用 useContext(MyContext) 的组件 — 无论它们实际消费的值是否真的变化了。
这是关键的效率:不调用 useContext 的中间组件不会被重新渲染。Context 会跳过它们。只有实际的消费者会被重新渲染。
创建一个 context 对象。接受一个默认值,当树中没有 Provider 在消费者上方时使用。
向所有后代消费者广播值。嵌套的 Provider 覆盖外层的。值应该被记忆化以防止不必要的重新渲染。
使组件订阅 context 的 hook。每当 context 值变化时重新渲染。
Context 直接跳转到消费者 — 中间组件不会重新渲染,除非它们也调用了 useContext。
React 使用 Object.is() 比较 context 值。每次渲染时传递新的对象字面量 '{''}' 会触发所有消费者重新渲染,即使内容相同。
1const ThemeContext = React.createContext('light'); // default23// Provider — wrap at the top level4function App() {5 const [theme, setTheme] = useState('dark');67 // ⚠️ Memoize the value to avoid re-rendering all consumers8 // on every App render, even when theme hasn't changed9 const value = useMemo(() => ({ theme, setTheme }), [theme]);1011 return (12 <ThemeContext.Provider value={value}>13 <Layout />14 </ThemeContext.Provider>15 );16}1718// Consumer — anywhere in the subtree19function Button() {20 const { theme } = useContext(ThemeContext);21 return <button className={theme}>Click</button>;22}
Context 解决了属性透传的问题 — 仅仅为了将数据传递给深层嵌套的子组件就需要在许多层组件中传递 props。但 context 最适合真正全局或广泛共享的数据(认证、主题、语言)。对于频繁变化的值,优先使用 Zustand 或 Redux,以避免在每次变化时重新渲染所有消费者。
Your app has a global theme context providing colors, fonts, and dark/light mode. After adding it, every keystroke in a form input re-renders the entire app because the ThemeProvider is at the root.
The context value is `{ theme, toggleTheme }`. If the ThemeProvider's parent re-renders for ANY reason (new route, state change), a new object is created → all consumers re-render. With 50+ themed components, every parent re-render triggers 50+ unnecessary re-renders.
Split into two contexts: `ThemeValueContext` (read-only, changes only on toggle) and `ThemeDispatchContext` (stable setter function). Components that only READ the theme subscribe to ThemeValueContext. Components that only TOGGLE subscribe to ThemeDispatchContext. Memoize both values with useMemo.
Takeaway: Split contexts by update frequency: separate rarely-changing data (theme colors) from frequently-changing data (user input). This prevents the 'context-triggers-everything' problem and is the pattern used by Redux, Zustand, and Jotai internally.
A settings page passes `locale`, `currency`, and `dateFormat` through 6 levels of nesting: Settings → Layout → Panel → Section → Field → Label. Adding a new preference requires editing 6 components.
Prop drilling creates tight coupling — every intermediate component must know about and forward props it doesn't use. Adding, renaming, or removing a preference requires changing every component in the chain. The intermediate components also re-render when props change even though they don't USE the data.
Create a `PreferencesContext` and consume it directly in Label with `useContext(PreferencesContext)`. Intermediate components (Layout, Panel, Section, Field) don't receive or forward preference props. The preferences data 'teleports' from Provider to consumer, skipping all intermediate components.
Takeaway: Context eliminates prop drilling for cross-cutting concerns (theme, auth, locale, feature flags). Use it when data needs to be available deep in the tree and passing it through 3+ levels of components that don't use it. For frequently-updating state, consider a state manager instead.