likes
comments
collection
share

浅谈 React Context

作者站长头像
站长
· 阅读数 41

什么是 Context

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

Context 设计的目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。

如何使用 Context

export const MyContext = React.createContext(defaultValue);

<MyContext.Provider value={/* 某个值 */}> 消费组件 </MyContext.Provider>

使用 React.createContext 创建一个 Context 对象,每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。

Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。

使用 Context.Consumer 在函数式组件中完成订阅:

import { MyContext } from 'xxx';

<MyContext.Consumer>
  // 这个函数接收当前的 context 值,返回一个 React 节点
  {value => /* 基于 context 值进行渲染 */}
</MyContext.Consumer>

当 React 渲染一个订阅了这个 Context 对象的组件,该消费组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值(value)。

当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此 consumer 组件在其祖先组件退出更新的情况下也能更新。 判断新旧值的变化,是通过与 Object.is() 相同的算法来确定的。

只有当组件所处的树中没有匹配到 Provider 时,defaultValue 参数才会生效,将 undefined 传递给 Provider 的 value,defaultValue 也不会生效。

在嵌套组件中更新 Context

从一个在组件树中嵌套很深的组件中更新 context 是很有必要的,这种场景下,可以通过 context 传递一个函数,使得 consumers 组件能够更新 context。

消费多个 Context

为了确保 context 快速进行重渲染,React 需要使每一个 consumers 组件的 context 在组件树中成为一个单独的节点:

import { createContext } from 'react';

// Theme context,默认的 theme 是 “light” 值
const ThemeContext = createContext('light');

// 用户登录 context
const UserContext = createContext({ name: 'Jack' });

const Layout = () => {
  return (
    <>
      <Sidebar />
      <Content />
    </>
  );
};

const App = ({ theme, signedInUser }) => {
  return (
    <ThemeContext.Provider value={theme}>
      <UserContext.Provider value={signedInUser}>
        <Layout />
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
};

// 一个组件可能会消费多个 context
const Content = () => {
  return (
    <ThemeContext.Consumer>
      {(theme) => (
        <UserContext.Consumer>
          {(user) => <ProfilePage user={user} theme={theme} />}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
};

export default App;

注意事项

因为 context 会使用参考标识来决定何时进行渲染,这里可能会有一些陷阱,当 Provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染。举个例子:

function App() {
    return (
        <MyContext.Provider value={{something: 'something'}}>
            <Toolbar />
        </MyContext.Provider>
    );
}

每当 Provider 重渲染时,上面的代码会重渲染所有下面的 consumers 组件,因为 value 属性总是被赋值为新的对象。

为了防止这种情况,可以将 value 状态提升到父节点的 state 里:

function App() {
    const [value, setValue] = useState({
        something: 'something'
    });

    return (
        <MyContext.Provider value={value}>
            <Toolbar />
        </MyContext.Provider>
    );
}

使用 Context 之前的考虑

Context 主要的应用场景在于很多不同层级的组件需要访问同样的一些数据。请谨慎使用,因为这会使得组件的复用性变差。

如果只是想避免层层传递一些属性,组件组合有时候是一个比 Context 更好的解决方案。

所谓组件组合,可以理解为将组件自身传递下去。这种对组件的控制反转减少了应用中需要传递的 props 数量,在很多场景下会使代码更加干净,对组件也会有更多的把控:

import { useState } from 'react';

import Father from './Father';
import Grandfather from './Grandfather';
import Grandson from './Grandson';

const App = () => {
  const [value, setValue] = useState('哈哈哈');
  const grandson = <Grandson value={value} />;
  const father = <Father grandson={grandson} />;

  return (
    <div>
      <Grandfather father={father} />
    </div>
  );
};

export default App;

但是,这并不适用于每个场景。将逻辑提升到组件树的更高层级来处理,会使得这些高层组件变得更复杂,并且会强迫低层组件适应这样的形式,这可能不是我们想要的。

最后

如果文中有错误或者不足之处,欢迎大家在评论区指正。

你的点赞是对我莫大的鼓励!感谢阅读~

转载自:https://juejin.cn/post/7203282880230359098
评论
请登录