React Context+Hooks API => 理想的状态管理
嗨!React 功能强大,用途广泛。但有时我们需要全局状态,例如 UI 主题或语言环境偏好设置。通常情况下,我们会通过 props 将状态传递给子组件。但对于全局状态,我们需要将 props 多次传递到组件树的各个层级,就像土豆的根一样(如果你看过Women Of React 2020的演讲的话)。这就造成了一种被称为“prop 钻”的繁琐现象。这意味着我们需要将 props 从祖父组件传递到父组件,再传递到子组件,以此类推。
为了解决这个问题,你可以使用 Redux 之类的工具,这当然是一个不错的解决方案,但它会重构你的整个代码,并需要编写大量的样板代码。这使得它不适合轻量级实现。不过请记住,它不会影响性能。
那我们该怎么办?
进入 React Context API。
上下文提供了一种在组件树中传递数据的方法,而无需在每一层手动传递 props。
这是 React 官方文档的介绍。Context 是在 React 16.3 版本中引入的,它解决了全局状态管理的问题。Context 通常被认为是 Redux 的轻量级替代方案,能够提供更简洁的代码。那么,让我们开始吧!
那么,让我们创建一个简单的 React 应用。使用 `react-app create-react-app-build` 生成一个应用。然后在 `react-app-build` 中编写以下代码。App.js
function App() {
return (
<div className="App">
<AppBar theme="white" />
</div>
);
}
function AppBar({theme}) {
return(
<div className="AppBar">
<ThemedButton theme={theme}/>
</div>
);
}
function ThemedButton({theme}) {
return(
<div>
<button style={{backgroundColor: theme}} />
</div>
)
}
export default App;
如上所示,我们需要将主题属性传递给所有组件,以便将其应用于子元素。这对于三个组件来说或许不错,但想象一下一个完整的动态网站,其组件树可能非常庞大且复杂。
那么我们来用 React Context 试试同样的方法。不过在使用 Context 之前,你需要记住它并不适用于组件数量少、属性也少的情况。对于这种情况,简单的属性线程和组件组合会更加简单。所以请谨慎使用 Context。
const ThemeContext = React.createContext('white');
function App() {
return (
<div className="App">
<ThemeContext.Provider value={"black"}>
<AppBar />
</ThemeContext.Provider>
</div>
);
}
function AppBar() {
return(
<div className="AppBar">
<ThemedButton />
</div>
);
}
function ThemedButton() {
return(
<div>
<ThemeContext.Consumer>
{value => <button style={{backgroundColor: value}} />}
</ThemeContext.Consumer>
</div>
)
}
export default App;
好的,这里有很多新代码,如果你仔细观察,你会发现 ` AppBarand`ThemedButton组件中的 props 都消失了。这是怎么回事呢?让我们来逐一分析一下。
所以请注意代码片段顶部的这行代码:
const ThemeContext = React.createContext('white');
这就是创建 React Context 对象的过程。每个 Context 对象都包含一个 Provider 和一个 Consumer。如果您参考上面的代码,就会看到它们。
提供者和消费者
//Provider
<ThemeContext.Provider value={"black"}>
</ThemeContext.Provider>
//Consumer
<ThemeContext.Consumer>
</ThemeContext.Consumer>
Provider 组件允许使用组件订阅上下文更改。
它接受一个 value 属性,该属性将传递给作为此提供程序子组件的使用者。因此,一个提供程序可以连接到多个使用者。提供程序甚至可以嵌套,以覆盖组件树中更深层的值。
当 Provider 的 value prop 发生变化时,所有作为 Provider 后代的消费者都会重新渲染。
Consumer 组件是负责订阅上下文变化的组件。但是,Consumer 组件需要一个类似render props 的函数作为子组件。该函数接收当前上下文值并返回一个 React 节点。
传递给函数的 value 参数将等于树状结构中与此上下文最接近的 Provider 的 value 属性。因此,在上面的代码中,我使用该值来为按钮着色。
<ThemeContext.Consumer>
{value => <button style={{backgroundColor: value}} />}
</ThemeContext.Consumer>
获取上下文的值。
现在您已经了解如何使用 Context API 了。但是,如果您查看 Provider 并思考其使用场景,您很快就会意识到,从 JSX 代码中提取上下文以实现其他功能有点困难。当然,有一些变通方法,但这并非理想之选。您可能在某些地方见过类似的方法,但那通常是遗留问题。
如果ThemedButton它是一个类组件,我们就可以使用 contextType 提取上下文。
类上的 `on`属性contextType property可以赋值给一个 `Context` 对象。这样,你就可以使用 `.` 来获取该 `Context` 类型的当前值this.context。你可以在任何生命周期方法(包括渲染函数)中引用它。所以我们可以这样实现它。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
但是,我们使用的是轻量级函数式组件和 Hooks!所以,让我们对现有代码进行一些重构。
import React, { useContext } from 'react';
const ThemeContext = React.createContext('white');
function App() {
return (
<div className="App">
<ThemeContext.Provider value={"black"}>
<AppBar />
</ThemeContext.Provider>
</div>
);
}
function AppBar() {
return(
<div className="AppBar">
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext)
return(
<div>
<button style={{backgroundColor: theme}} />
</div>
)
}
export default App;
这里我们使用了useContext钩子函数,它相当于函数式组件contextType。有了它useContext,我们就可以省去提供程序,直接在 JSX 代码之外获取当前上下文值。
更新我们的背景
更新上下文就像更新状态一样简单。使用函数式组件,我们可以useState通过传递一个函数来实现这一点,该函数将更新我们的上下文。
import React, { useState, useContext } from "react";
const ThemeContext = React.createContext({ theme: "white", toggler: () => {} });
function App() {
const [color, setColor] = useState("white");
const toPass = {
theme: color,
toggler: () => {
return color === "white" ? setColor("black") : setColor("white");
},
};
return (
<div className="App">
<ThemeContext.Provider value={toPass}>
<AppBar />
</ThemeContext.Provider>
</div>
);
}
function AppBar() {
return (
<div className="AppBar">
<ThemedButton />
</div>
);
}
function ThemedButton() {
const context = useContext(ThemeContext);
return (
<div>
<button
style={{ backgroundColor: context.theme }}
onClick={context.toggler}
/>
</div>
);
}
export default App;
如上所示,color状态是通过一个切换函数来操作的,该函数通过上下文传递下去。然后,子组件中的按钮会调用全局上下文中的切换函数,从而更新全局上下文。
好了!现在你知道如何利用 Context 和 Hooks 来维护组件树中的全局状态了。
如果您想深入了解 Context,请阅读官方文档。
文章来源:https://dev.to/gdsckiitdev/react-context-hooks-api-ideal-state-management-23ag