2022-09-21前沿技术00
请注意,本文编写于 613 天前,最后修改于 613 天前,其中某些信息可能已经过时。

精读《如何安全地使用 React context》

本期精读文章是:How to safely use React context

1 引言

在 React 源码中,context 始终存在,却在 React 0.14 的官方文档中才有所体现。在目前最新的官方文档中,仍不建议使用 context,也表明 context 是一个实验性的 API,在未来 React 版本中可能被更改。那么哪些场景下需要用到 context,而哪些情况下应该避免使用,context 又有什么坑呢?让我们一起来讨论一下。

2 内容概要

React context 可以把数据直接传递给组件树的底层组件,而无需中间组件的参与。Redux 作者 Dan Abramov 为 contenxt 的使用总结了一些注意事项:

  • 如果你是一个库的作者,需要将信息传递给深层次组件时,context 在一些情况下可能无法更新成功。
  • 如果是界面主题、本地化信息,context 被应用于不易改变的全局变量,可以提供一个高阶组件,以便在 API 更新时只需修改一处。
  • 如果库需要你使用 context,请它提供高阶组件给你。

正如 Dan 第一条所述,在 React issue 中,经常能找到 React.PureComponent、shouldComponentUpdate 与包含 Context 的库结合后引发的一些问题。原因在于 shouldComponentUpdate 会切断子树的 rerender,当 state 或 props 没有发生变化时,可能意外中断上层 context 传播。也就是当 shouldComponentUpdate 返回 false 时,context 的变化是无法被底层所感知的。

因此,我们认为 context 应该是不变的,在构造时只接受 context 一次,使用 context,应类似于依赖注入系统来进行。结合精读文章的示例总结一下思路,不变的 context 中包含可变的元素,元素的变化触发自身的监听器实现底层组件的更新,从而绕过 shouldComponentUpdate。

最后作者提出了 Mobx 下的两种解决方案。context 中的可变元素可用 observable 来实现,从而避免上述事件监听器编写,因为 observable 会帮你完成元素改变后的响应。当然 Provider + inject 也可以完成,具体可参考精读文章中的代码。

3 精读

context 的使用场景

In some cases, you want to pass data through the component tree without having to pass the props down manually at every level.

context 的本质在于为组件树提供一种跨层级通信的能力,原本在 React 只能通过 props 逐层传递数据,而 context 打破了这一层束缚。

context 虽然不被建议使用,但在一些流行库中却非常常见,例如:react-reduxreact-router。究其原因,我认为是单一顶层与多样底层间不是单纯父子关系的结果。例如:react-redux 中的 Provider,react-router 中的 Router,均在顶层控制 store 信息与路由信息。而对于 Connect 与 Route 而言,它们在 view 中的层级是多样化的,通过 context 获取顶层 Provider 与 Router 中的相关信息再合适不过。

context 的坑

  • context 相当于一个全局变量,难以追溯数据源,很难找到是在哪个地方中对 context 进行了更新。
  • 组件中依赖 context,会使组件耦合度提高,既不利于组件复用,也不利于组件测试。
  • 当 props 改变或是 setState 被调用,getChildContext 也会被调用,生成新的 context,但 shouldComponentUpdate 返回的 false 会 block 住 context,导致没有更新,这也是精读文章的重点内容。

4 总结

正如精读文章开头所说,context 是一个非常强大的,具有很多免责声明的特性,就像伊甸园中的禁果。的确,引入全局变量似乎是应用混乱的开始,而 context 与 props/state 相比也实属异类。在业务代码中,我们应抵制使用 context,而在框架和库中可结合场景适当使用,相信 context 也并非洪水猛兽。

本文作者:前端小毛

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!