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的基本使用有三个方面:

diagram 300x205

第一,定义一个 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是一个简单的列表,每列表项显示城市名和当下气温;用户可在下面添加想了解的城市:

Multi-city Weather app

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),则注意它的性能,并根据需要进行重构来改善性能,当然只有在有明显性能损耗时。


  1. 单一的V只有状态,没有状态管理,状态管理只有在复合的Vx组件中才有。
裸男
Nakeman.cn 2023 Build by Gatsby and Tailwind, Deploy on Netlify.