Content #
React 组件之间的状态传递只有一种方式,那就是通过 props。这就意味着这种传递关系只能在父子组件之间进行。
如果要跨层次,或者同层的组件之间要进行数据的共享,那应该如何去实现呢?这其实就涉及到一个新的命题:全局状态管理。
为此,React 提供了 Context 这样一个机制,能够让所有在某个组件开始的组件树上创建一个 Context。这样这个组件树上的所有组件,就都能访问和修改这个 Context 了。那么在函数组件里,我们就可以使用 useContext 这样一个 Hook 来管理 Context。
useContext 的 API 签名如下:
const value = useContext(MyContext);
正如刚才提到的,一个 Context 是从某个组件为根组件的组件树上可用的,所以我们需要有 API 能够创建一个 Context,这就是 React.createContext API,如下:
const MyContext = React.createContext(initialValue);
这里的 MyContext 具有一个 Provider 的属性,一般是作为组件树的根组件。这里我仍然以 React 官方文档的例子来讲解,即:一个主题的切换机制。代码如下:
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
// 创建一个 Theme 的 Context
const ThemeContext = React.createContext(themes.light);
function App() {
// 整个应用使用 ThemeContext.Provider 作为根组件
return (
// 使用 themes.dark 作为当前 Context
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
// 在 Toolbar 组件中使用一个会使用 Theme 的 Button
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
// 在 Theme Button 中使用 useContext 来获取当前的主题
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{
background: theme.background,
color: theme.foreground
}}>
I am styled by theme context!
</button>
);
}
Context 看上去就是一个全局的数据,为什么要设计这样一个复杂的机制,而不是直接用一个全局的变量去保存数据呢?
答案其实很简单,就是为了能够进行数据的绑定。当这个 Context 的数据发生变化时,使用这个数据的组件就能够自动刷新。但如果没有 Context,而是使用一个简单的全局变量,就很难去实现了。
不过刚才我们看到的其实是一个静态的使用 Context 的例子,直接用了 thems.dark 作为 Context 的值。那么如何让它变得动态呢?
比如说常见的切换黑暗或者明亮模式的按钮,用来切换整个页面的主题。事实上,动态 Context 并不需要我们学习任何新的 API,而是利用 React 本身的机制,通过这么一行代码就可以实现:
<ThemeContext.Provider value={themes.dark}>
可以看到,themes.dark 是作为一个属性值传给 Provider 这个组件的,如果要让它变得动态,其实只要用一个 state 来保存,通过修改 state,就能实现动态的切换 Context 的值了。而且这么做,所有用到这个 Context 的地方都会自动刷新。比如这样的代码:
// ...
function App() {
// 使用 state 来保存 theme 从而可以动态修改
const [theme, setTheme] = useState("light");
// 切换 theme 的回调函数
const toggleTheme = useCallback(() => {
setTheme((theme) => (theme === "light" ? "dark" : "light"));
}, []);
return (
// 使用 theme state 作为当前 Context
<ThemeContext.Provider value={themes[theme]}>
<button onClick={toggleTheme}>Toggle Theme</button>
<Toolbar />
</ThemeContext.Provider>
);
}
在这段代码中,我们使用 state 来保存 theme,从而达到可以动态调整的目的。
可以看到,Context 提供了一个方便在多个组件之间共享数据的机制。不过需要注意的是,它的灵活性也是一柄双刃剑。你或许已经发现,Context 相当于提供了一个定义 React 世界中全局变量的机制,而全局变量则意味着两点:
- 会让调试变得困难,因为你很难跟踪某个 Context 的变化究竟是如何产生的。
- 让组件的复用变得困难,因为一个组件如果使用了某个 Context,它就必须确保被用到的地方一定有这个 Context 的 Provider 在其父组件的路径上。
所以在 React 的开发中,除了像 Theme、Language 等一目了然的需要全局设置的变量外,我们很少会使用 Context 来做太多数据的共享。需要再三强调的是, Context 更多的是提供了一个强大的机制,让 React 应用具备定义全局的响应式数据的能力。
Viewpoints #
From #
04|内置 Hooks(2):为什么要避免重复定义回调函数?