I Want To Know React - 初探 Context

在本章中,我们将介绍 React Context。React Context 与 props 相同,是一种资料传递的方式,然而 React Context 可以解决某些 props 在特殊情境中遇到的难点。

接着,就兰更近一步介绍 Context 的详细功能、解决的问题以及使用情境吧!

React - Context

何谓 Context

React Context 是一种利用向下广播来传递资料的方式。

与 props 不同,context 不必将资料传递给中间层的 component 就能够把资料分享给下层 component。

到这边为止,读者可能会想说为何需要 context,为何不用 Props 就好呢?以下先来看看 props 会有哪些缺点吧。

Props 的缺点

Props 有一个特点,就是 props 不可跳层传递。

如果要将资料从上层 component 传递到较下层的 component 的话,则需要经过每一个中间层 component 的 props 才可送达目标的下层 component。

这种特性是 props 的优点。其资料流路径明确,只要照着每一层 component 的 props 往上追朔即可找到资料源头。然而当我们要把一个全域性的资料(e.g. UI 主题、时区、语系 ...etc)传给许多下层 component 时,使用 props 就会变成一个噩梦。每一个中间层 component 都必须定义此 props 才能把资料送达目的地。这会导致几个问题:

  • 污染中间层 component。不需要资料的中间层 component 也被迫定义对应的 props

  • 当传递层数与传递的目标 component 过多时,会造成修改 props 与追朔资料流不易

    此时即使只是微小的修改,也需要修改众多的中间层 component 才能达到目的。

以下是一个不使用 Context 传递 theme 的范例:

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  // The Toolbar component must take an extra "theme" prop
  // and pass it to the ThemedButton. This can become painful
  // if every single button in the app needs to know the theme
  // because it would have to be passed through all components.
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />;
  }
}

范例中,Toolbar 就算不需要 theme 的资料,也被迫定义 theme prop,以将 theme 资料传递给更下层的 ThemeButton

可以想像一下如果网页中的每个 button 都必须接收 theme 的话,整个专案会变得多混乱,因为资料需要从最上层 component(App)经过每个中间层传递到最下层 component(每个 Button)。这几乎相当於整个专案的 component 都会有 theme 这个 prop 了。

为何需要 Context

在明确知道 props 的缺点後,相信读者已经很清楚 context 存在的意义了。

Context 就是为了解决 props 必须要逐层传递的缺点而存在。

使用 context 就可以让资料以 "广播" 的方式传递给下层 component。中间层的 component 则完全不需要为了资料做任何的改动。如此一来,就可以避免由最上层 component 传递资料到最下层的多个 component 时,需要污染专案所有中间层 component 的问题了。

现在就来使用 context 来修改上一个 props 传递的范例:

// Context lets us pass a value deep into the component tree
// without explicitly threading it through every component.
// Create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // Use a Provider to pass the current theme to the tree below.
    // Any component can read it, no matter how deep it is.
    // In this example, we're passing "dark" as the current value.
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // Assign a contextType to read the current theme context.
  // React will find the closest theme Provider above and use its value.
  // In this example, the current theme is "dark".
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

可以看到,中间层 component Toolbar 不再需要为了传递 theme 而额外定义一个 prop。更下层的 ThemedButton 仍可以避过中间层收到 theme 的资料。如此就能避免将 theme 逐层传递的问题了。

何时使用 Context

然而并不是所有情境都适合使用 context。React 官方推荐只有在以下情境时,才使用 context。

  • 要把全域性资料传递给很多 components 时

所谓的全域性资料代表整个 App 都需要的资料,例如使用者资讯、时区设定、语系、UI 主题

...etc。上个段落的 theme 程序就是一很好的范例。

何时不应该使用 Context

需要注意的是,不应该只为了避免传递多层 props 而使用 context。

如上一个段落所说,开发者只应该把 context 用在要把全域性资料传递很多 component 的状况下。

滥用 context 则可能造成 component 重用性降低,另外由於 context 可跳层传递资料的特性,也可能造成追踪程序码不易。

如果只是想避免中间层 component 的 props 被污染的话,可以使用 composition 技巧来解决。我们将在下一个段落详细说明。

使用 composition 避免传递细节 props

在很多时候,当我们想要传递一些只有特定(非全域性资料)下层 component 需要的资料时,就会遇到中间层 component 被一并污染的问题。在这种状况下,随着下层 component 支援的 props 越来越多,中间层 component 就会被迫传递更多自己不需要的 prop 资料。

然而如上个段落提过,开发者不应该只为了避免传递多层 props 而使用 context。而一个不使用 context 的可行解法是使用 composition,其技巧为:

  • 把整个下层 component 作为 props 传递下去

使用了这种依赖反转的概念,我们就可以把控制内容的权力转移到上层 component 上了。中间层的 component 不再需要为下层 component 的每个 prop 都定义一个额外的 prop,只要需要定义一个 prop 负责把整个 React element 传下去即可。

举例来说,如果在某个页面中要显示使用者的大头贴 Avatar,然而这个大头贴的资料储存在最上层 component 中,需要使用 props 由上传下去:

<Page user={user} avatarSize={avatarSize} avatarShape={shape} />
// ... which renders ...
<PageLayout user={user} avatarSize={avatarSize} avatarShape={shape} />
// ... which renders ...
<NavigationBar user={user} avatarSize={avatarSize} avatarShape={shape} />
// ... which renders ...
<Link href={user.permalink}>
  <Avatar user={user} size={avatarSize} shape={shape} />
</Link>

这时候就可以发现,所有中间层 component 必须要定义 Avatar 的显示资讯 useravatarSizeavatarShape。这个情况绝对不是我们乐见的,想像今天 Avatar 需要更多的 props 资料,那中间层 component 的 props 甚至会被一些下层 component 的 props 所淹没。

此时来尝试使用 composition 解法吧。把 LinkAvatar 整个元素包成一个 userLink React elment 当作 props 往下传。这样中间层的 component 就不用知道各种 Avatar 的细节 props 了:

function Page(props) {
  const user = props.user;
  const userLink = (
    <Link href={user.permalink}>
      <Avatar user={user} size={props.avatarSize} />
    </Link>
  );
  return <PageLayout userLink={userLink} />;
}

// Now, we have:
<Page user={user} avatarSize={avatarSize} />
// ... which renders ...
<PageLayout userLink={...} />
// ... which renders ...
<NavigationBar userLink={...} />
// ... which renders ...
{props.userLink}

然而这个方式会有个缺点:

  • 因为要组合下层 component,所以会让上层 component 的内容较为复杂

因此读者还是应该视情境选择要不要使用此技巧。

小结

在这一章节中,我们学到了 React context 是一种利用向下广播来传递资料的方式。

此方法可以解决 props 必须要一层层向下传递的缺点。

而 React 官方推荐使用 context 是在要将全域性资料(e.g. 使用者资讯、时区设定、语系、UI 主题 ...etc)向下传递给很多底层 component 时才应该使用。开发者不应该只会了避免传递过多层 props 就使用 context。

如果是要避免中间层 component 传递过多细节 props 的话,开发者可以使用 composition 的技巧,把整个下层 component 作为 props 传递下去即可。

下一章节中,我们将学习如何使用 Context 语法。

参考资料


<<:  Day26:管理 LXC 的好工具 —— Docker

>>:  故事二十六:今天来玩玩资料透视表!

[Day30] 今天是最後一天啦~

今天是我最後一次以git的主题跟各位沟通啦~ 说实话,觉得要坚持30天,天天发文真的有点难馁XD ...

NestJs 延伸篇 - Federation 实作

上一篇我们建立了 gateway ,也把 Task Service 安装了 federation 的...

[Day 28] 建立注册的画面及功能(十二) - 寄出注册通知信

寄送会员通知信 Laravel基於SwiftMailer函式库开发了一套邮件套件, 可以支援多种服务...

使用 Ubuntu Server 与 Docker 建立 Gitea 程序储存库

在资安越来越严苛的情况下 公司内部通常需要一个版本控制的储存库以方便进行存储观看程序与版本历史 这时...

WordPress Google Search Console 安装教学 让新文章马上列入搜寻名单

新建置的 WordPress 辛苦写了几篇文章,可是这时候跟本就不会被 Google 搜寻看到我的文...