用 context 传递数据而无需 prop drilling,同时把它的重新渲染和局限性控制在手中。
在 Kodokon 中打开本课Prop drilling(逐层传 prop)指的是那些穿过三四个中间组件却在那里根本用不到、只是为了抵达树的某个叶子节点的 prop。它们经过的每一层都被耦合上了自己毫不关心的数据。context 解决的正是这个传输问题:createContext 创建通道,一个 provider 发布一个值,useContext 从任意后代读取它。要记住一个细微差别:context 传输状态,它并不管理状态——值仍然存在于某个组件里,这里就是 provider。
import { createContext, useMemo, useState } from "react";
const ThemeContext = createContext(null);
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("dark");
const value = useMemo(
() => ({ theme, setTheme }),
[theme]
);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}当 provider 的值改变时,所有消费者都会重新渲染,即便是嵌套很深的——而且 React.memo 保护不了它们,因为它比较的是 prop,不是 context。经典陷阱让情况雪上加霜:写 value={{ theme, setTheme }} 会在 provider 每次渲染时创建一个新对象,于是产生新引用,于是即便 theme 没变也会级联式重新渲染。上面的 useMemo 就是为此而生。再配上一个在 provider 之外会大声报错的访问 hook,整套设置就完整了。
import { useContext } from "react";
function useTheme() {
const context = useContext(ThemeContext);
if (context === null) {
throw new Error(
"useTheme must be inside <ThemeProvider>"
);
}
return context;
}在创建 context 之前,先把组合(composition)这个选项用尽。如果某个中间层除了转发一个 prop 之外什么都不做,就把结构反过来:在树的顶端、数据所在的地方构建好元素,再通过 children 或专门的 prop 往下传。这样中间层就变得通用了,数据也不再经过任何用不到它的组件。
function App({ currentUser }) {
return (
<Layout
header={<TopBar user={currentUser} />}
content={<Dashboard />}
/>
);
}