本周精读的文章是 [React's new Context API](https://tinyletter.com/kentcdodds/letters/react-s-new-context-api)。

## 1 引言

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

## 2 概述

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

新版 Context 语法是这样的：

```typescript
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 并赋初始值。返回的对象包含 `provider` 与 `consumer`。
- `provide` 是一个容器，它所有的子元素都能通过 `consumer` 访问到这个 Context 的值。

其实这种思想在 [react-broadcast](https://github.com/ReactTraining/react-broadcast) 已经被实现，现在变成了官方 API。

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

```typescript
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](https://github.com/FormidableLabs/react-context-composer) 这种库优雅的解决。摘自 [如何解读 react 16.3 引入的新 context api@淡苍](https://www.zhihu.com/question/267168180/answer/324650336)

### 绕过 shouldComponentUpdate

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

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

### 是否还需要 redux

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

在之前一篇精读 [前端数据流哲学](https://zhuanlan.zhihu.com/p/33382396) 中，我提到了 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](https://ant.design/components/locale-provider-cn/)。

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

## 4 总结

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

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

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

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

```typescript
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](https://github.com/dt-fe/weekly/issues/64)

**如果你想参与讨论，请[点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，每周五发布。**
