14

React Context API是什么以及基本使用

 3 years ago
source link: http://nakeman.cn/engineering/webprogramming/what-is-react-context-api.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

React Context API 是 针对所谓 状态管理(state management) [em] 而设计的API。 Context API 以一种更直接有效的方式解决了早期使用 props 来处理嵌套UI的状态共享的问题。

EM:单一的V只有状态,没有状态管理,状态管理只有在复合的Vx组件中才有。

那什么是 状态管理呢? Context API 优于props的地方在哪里呢?Context API 又如何使用呢?

本文摘译自《 React Context API: Using React Context with APIs effectively 》。

Table of Contents

  • React Context API基本使用
    • 第一,定义一个 Context对象,作为状态容器;
    • 第二,在需要分享状态节点的一个「公共父节点」上安装Context对象;
    • 第三,访问 Context 的数据;
  • 一个查看多个城市气温的APP
  • Context API的使用模式

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.png

第一,定义一个 Context对象,作为状态容器;

例如假设我们要开发一个用户帐户页(AccountView),页内很多UI要访问「用户信息」(此数据一般从数据库读到),我们可创建一个UserContext进行状态共享:

// Here we provide the initial value of the context
const UserContext = React.createContext({
  currentUser: null,
});

第二,在需要分享状态节点的一个「公共父节点」上安装Context对象;

例如在 用户帐户页顶层节点(AccountView)上安装 UserContext ,以一个 Provider节点 [em] 的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>
  );
};

EM:UserContext 不是一个V,UserContext.Provider 似乎是一个V,才可使用JSX语法,但实质UserContext.Provider不应该是一个V,所以其实这种写法挺怪异;Router也这种写法,“一切皆组件”的思想其实不成熟。

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK