React Context API是什么以及基本使用
React Context API 是 针对所谓 状态管理(state management)注1 而设计的API。 Context API 以一种更直接有效的方式解决了早期使用 props 来处理嵌套UI的状态共享的问题。
那什么是 状态管理呢? Context API 优于props的地方在哪里呢?Context API 又如何使用呢?
本文摘译自《React Context API: Using React Context with APIs effectively》。
UI状态管理技术
实际应用中的React 程序,它的交互UI都是复杂的,有着复杂的嵌套层次关系,这些嵌套的UI组件都需 共享中间计算数据(状态),甚至是计算功能(函数)来实现交互功能。
目前有三种 实现共享中间计算数据 的技术:
-
第一,是组件的模板参数 props。这个方法最大的优点是直观,缺点是当组件层次较复杂时没有针对性,计算不相关的组件可能会看这个props。还有一个问题,就是props形式参数,它不是用来“通信”共享数据的,有可能螺丝刀当锤子用(待证);
-
第二,是增加一个中间 状态管理的对象,像Redux这样的。这是props技术的另一个极端,优点是它非常有针对性,然而缺点是成本高,包括学习、开发和程序性能。状态管理的库是有用的,它适用于某些大型项目,但是对于我们这里的任务(局部小范围的状态共享),状态管理库不是最优的方案;
-
第三,是React Context API。将计算的共享数据保存在一个通用的顶层父组件上(叫 Provider Component),然后内层任务层次的子组件(叫 Consumer Components)可按需直接访问。这种方法有和 Props技术相似的比较直观的优点;
总结一点,在以下两种情况下,适用React Context API进行状态共享:
- 第一,组件嵌套层次深,两个共享数据的组件 离得较远(如果很“近”,直接使用props,官方也这样建议);
- 第二,父组件需要和 很多个子组件 共享数据;
React Context API基本使用
React Contexts的基本使用有三个方面:
第一,定义一个 Context对象,作为状态容器;
例如假设我们要开发一个用户帐户页(AccountView),页内很多UI要访问「用户信息」(此数据一般从数据库读到),我们可创建一个UserContext进行状态共享:
// Here we provide the initial value of the context
const UserContext = React.createContext({
currentUser: null,
});
第二,在需要分享状态节点的一个「公共父节点」上安装Context对象;
例如在 用户帐户页顶层节点(AccountView)上安装 UserContext ,以一个Provider节点的value props指定,并以包含子组件的形式定义嵌套关系:
const AccountView = (props) => {
const [currentUser, setCurrentUser] = React.useState(null);
return (
{/* Here we provides the actual value for its descendents */}
<UserContext.Provider value={{ currentUser: currentUser }}>
<AccountSummary/>
<AccountProfile/>
</UserContext.Provider>
);
};
第三,访问 Context 的数据;
假设这里 AccountSummary还有三个子组件,是这些子组件(第三层)需要访问 UserContext;
// Here we don't use the Context directly, but render children that do.
const AccountSummary = (props) => {
return (
<AccountSummaryHeader/>
<AccountSummaryDashboard/>
<AccountSummaryFooter/>
);
};
以其中的AccountSummaryHeader为例,用一个useContext Hook将其勾进来:
const AccountSummaryHeader = (props) => {
// Here we retrieve the current value of the context
const context = React.useContext(UserContext);
return (
<section>
<h2>{context.currentUser.name}</h2>
</section>
);
};
注意,此例子使用了React最新Hooks技术,如果需要用Class,用法有少许不同,此处略。
一个查看多个城市气温的APP
此应用程序的UI是一个简单的列表,每列表项显示城市名和当下气温;用户可在下面添加想了解的城市:
Context的结构
const WeatherContext = React.createContext({
cities: [],
addCity: (name, temperature) => { },
});
很明显我们需要的一个城市数组(数据项至少有两个字段),还有,一个全局的 添加新城市的函数;
Context安装
function App() {
const [cities, setCities] = React.useState([]);
const addCity = (name, temperature) => {
const newCity = { name, temperature };
setCities(prevCities => [...prevCities, { name, temperature }]);
};
return (
<WeatherContext.Provider value={{
cities,
addCity,
}}>
...
<CityList />
<AddCityButton />
...
</WeatherContext.Provider>
);
}
这里特别要注意的是,顶层节点(被安装 context 的对象)V状态,和context value什么的关系;逻辑是,App这个V使用了useState Hook保存了cities状态数据,而这个cities通过WeatherContext共享给所有子节点V。
访问Context data
const CityList = (props) => {
const context = React.useContext(WeatherContext);
return (
<table className="city-list">
<thead>
...
</thead>
<tbody>
{context.cities.map((city, i) => (
<tr key={city.name}>
<td>{city.name}</td>
<td>{city.temperature}</td>
</tr>
))}
</tbody>
</table>
);
};
注意是使用了Hook;
访问Context func进行城市添加
const AddCityButton = (props) => {
const context = React.useContext(WeatherContext);
const [name, setName] = React.useState('');
const submit = () => {
context.addCity(name, Math.ceil(Math.random() * 10));
setName('');
};
return (
<div className="add-city-form">
<input value={name} onChange={(e) => setName(e.target.value)} />
<button onClick={submit}>Add</button>
</div>
);
};
这里注意,按键AddCityButton有一个name状态,为什么要一个中间状态,而不是直接使用input的输入值呢?
Context API的使用模式
文首说了,Context API是针对开发复杂嵌套结构组件(Vx)而设计的新技术,使用时需要注意最佳使用模式:
-
第一,当组件Vx嵌套不深时,例如只有两三层,可以直接使用props,不必一定使用Context,props不但直观,而且性能好;
-
第二,严格区分本地状态和全局状态,不要把本地状态保存在Context上,例如表单输入值是本地状,不是Context;
-
第三,将Context安装在最近一个公共父节点上,并不都是要安装在最顶层组件树节点上的;
-
最后,如果Context保存的是一个对象(object),则注意它的性能,并根据需要进行重构来改善性能,当然只有在有明显性能损耗时。
- 单一的V只有状态,没有状态管理,状态管理只有在复合的Vx组件中才有。↩