精读《React's new Context API》

本周精读的文章是 React's new Context API

1 引言

React 即将推出全新的 Context api,让我们一起看看。

2 概述

像 react-redux、mobx-react、react-router 都使用了旧 Context api,可谓 context 无处不在。

新版 Context 语法是这样的:

const ThemeContext = React.createContext('light')
class ThemeProvider extends React.Component {
  state = {theme: 'light'}
  render() {
    return ThemeContext.provide(this.state.theme, this.props.children)
  }
}

const ThemeConsumer = ({children}) => ThemeContext.consume(children)

class App extends React.Component {
  render() {
    <ThemeProvider>
      <ThemeConsumer>{val => <div>{val}</div>}</ThemeConsumer>
    </ThemeProvider>
  }
}
  • React.createContext 创建新的 Context 并赋初始值。返回的对象包含 providerconsumer
  • provide 是一个容器,它所有的子元素都能通过 consumer 访问到这个 Context 的值。

其实这种思想在 react-broadcast 已经被实现,现在变成了官方 API。

当然如果多个 Context 同时存在,可能会出现 jsx 的嵌套地狱,不过这个情况可以通过拆分模块,或者以如下方式定义多重 Consumer 来解决:

function ThemeAndLanguageConsumer({children}) {
  return (
    <LanguageConsumer>
      {language => (
        <ThemeConsumer>
          {theme => children({language, theme})}
        </ThemeConsumer>
      )}
    </LanguageConsumer>
  )
}

class App extends React.Component {
  render() {
    <AppProviders>
      <ThemeAndLanguageConsumer>
        {({theme, language}) => <div>{theme} and {language}</div>}
      </ThemeAndLanguageConsumer>
    </AppProviders>
  }
}

3 精读

最大的问题是,React17 会废除旧 Context 这个 api,许许多多的库需要升级,不过到时候也应该会出现 codemod 自动更新吧。

从 15.0 升级到 16.0 时因为项目中大量使用 React.PropTypes 的地方需要重构,从 16.0 升级到 17.0 时,就不是项目要升级了,而是比如 react-redux 这类库要偷偷升级 context 的用法,可见 React 大版本间生态完全兼容是不可能了。

Context 多层嵌套问题

一种方式是通过构造原文中描述的 ThemeAndLanguageConsumer 聚合 Consumer 解决,也可以使用比如 react-context-composer 这种库优雅的解决。摘自 如何解读 react 16.3 引入的新 context api@淡苍

绕过 shouldComponentUpdate

像 react-redux、mobx - react 这些库,都使用了 forceUpdate 绕过 shouldComponentUpdate 机制。原因是这些全局状态管理工具接管了自己的组件更新时机,纵使保留组件原本的更新机制,但当数据流发生变化时,需要绕过一切阻碍,直接触发目标组件的一整套渲染生命周期。

好在新的 Context api 也拥有如此特性,可以在 context 改变时,直接更新即使 shouldComponentUpdate: false 的组件。

是否还需要 redux

正如很多人说的,这要看我们是怎么使用 redux 了。

在之前一篇精读 前端数据流哲学 中,我提到了 redux、mobx、rxjs 这三大流派的竞争力。

其中 redux 其实是最没有竞争力的数据流框架,我们暂且抛开函数式和优雅性不说,从功能上说,看看 redux 到底做了啥?利用 react 特性,利用全局数据流解决组件间数据通信问题。抛开 react-redux,只看 redux,剩下不能再简单的 Action 与 Reducer。

再看 mobx,稍微好一点,其主打能力是自动追踪变量引用,当变量被修改时自动刷新视图,可见它的竞争力不仅仅在组件数据的打通,自动绑定带来的效率提升是一大亮点。

最后是 rxjs,其主打能力压根不在 react,核心竞争力在数据处理能力,与数据源的抽象,做到了副作用隔离在数据处理流程之外。

可见技术框架也是如此,核心竞争力在哪,未来就在哪。

是否 flux 多 store 思想再度崛起?

我觉得几乎不可能。

新的 Context API 给了开发者创造多个 context 的能力,可不是在项目中创建多个 store,制造混乱的呀。我们之前说过,除了数据流框架,像 react-router,或者一些国际化组件也会使用到 context 传递数据,本质上是需要 context 解决对数据透传的控制能力。

举个例子,国际化参数可以让组件一层一层透传,但调用到 node_modules 组件时,我们无法修改其 dom 结构,怎么让这个参数强制透传呢?所以必须使用 context 对所有需要国际化的组件注入 props,而这个注入变量由顶层 Provider 控制。比如 antd local-provider

然而共享一个 context 可能会冲突啊,现在你创建你的,我创建我的,咱们都互不影响,未来数据流框架大家会用的更爽,甚至一个项目可以同时并存多套数据流框架,因为互不影响嘛。

4 总结

然而新的 Context api 并不是银弹,无法解决所有问题,更不能解决业务组件与项目数据流绑定,导致的耦合问题。

因为不论怎么组织数据流,官方提供了怎样的 api,只要我们想给组件注入数据,那么注入的那个节点就一定依赖一个特性的项目环境,或者变量,比如某个 consumer

数据流框架也无法被取代,因为数据流框架的核心竞争力不在数据的依赖注入上,而是对数据的处理。

当然这次变化带来最乐观的改变是,react 拥有了一个稳定好用的依赖注入官方 api,在处理国际化这种需要拿 Context 小用一下的场景,可以不依赖第三方库了!代码如下:

const Locale = React.createContext({
  text: 'menu'
})

class MyComponent extends React.Component {
  render() {
    <Locale.consume>
      {text => (
        <span>This is the {text}</span>
      )}    
    </Locale.consume>
  }
}

class App extends React.Component {
  render() {
    <Locale.provide>
      <MyComponent />
    </Locale.provide>
  }
}

5 更多讨论

讨论地址是:精读《React's new Context API》 · Issue #64 · dt-fe/weekly

如果你想参与讨论,请点击这里,每周都有新的主题,每周五发布。

编辑于 2018-02-25

文章被以下专栏收录