本周精读的文章是 React's new Context API。
React 即将推出全新的 Context api,让我们一起看看。
像 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 并赋初始值。返回的对象包含 provider
与 consumer
。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>
}
}
最大的问题是,React17 会废除旧 Context 这个 api,许许多多的库需要升级,不过到时候也应该会出现 codemod 自动更新吧。
从 15.0 升级到 16.0 时因为项目中大量使用 React.PropTypes 的地方需要重构,从 16.0 升级到 17.0 时,就不是项目要升级了,而是比如 react-redux 这类库要偷偷升级 context 的用法,可见 React 大版本间生态完全兼容是不可能了。
一种方式是通过构造原文中描述的 ThemeAndLanguageConsumer
聚合 Consumer 解决,也可以使用比如 react-context-composer 这种库优雅的解决。摘自 如何解读 react 16.3 引入的新 context api@淡苍
像 redux、mobx - react 这些库,都使用了 forceUpdate
绕过 shouldComponentUpdate
机制。原因是这些全局状态管理工具接管了自己的组件更新时机,纵使保留组件原本的更新机制,但当数据流发生变化时,需要绕过一切阻碍,直接触发目标组件的一整套渲染生命周期。
好在新的 Context api 也拥有如此特性,可以在 context 改变时,直接更新即使 shouldComponentUpdate: false
的组件。
正如很多人说的,这要看我们是怎么使用 redux 了。
在之前一篇精读 前端数据流哲学 中,我提到了 redux、mobx、rxjs 这三大流派的竞争力。
其中 redux 其实是最没有竞争力的数据流框架,我们暂且抛开函数式和优雅性不说,从功能上说,看看 redux 到底做了啥?利用 react 特性,利用全局数据流解决组件间数据通信问题。抛开 react-redux,只看 redux,剩下不能再简单的 Action 与 Reducer。
再看 mobx,稍微好一点,其主打能力是自动追踪变量引用,当变量被修改时自动刷新视图,可见它的竞争力不仅仅在组件数据的打通,自动绑定带来的效率提升是一大亮点。
最后是 rxjs,其主打能力压根不在 react,核心竞争力在数据处理能力,与数据源的抽象,做到了副作用隔离在数据处理流程之外。
可见技术框架也是如此,核心竞争力在哪,未来就在哪。
我觉得几乎不可能。
新的 Context API 给了开发者创造多个 context 的能力,可不是在项目中创建多个 store,制造混乱的呀。我们之前说过,除了数据流框架,像 react-router,或者一些国际化组件也会使用到 context 传递数据,本质上是需要 context 解决对数据透传的控制能力。
举个例子,国际化参数可以让组件一层一层透传,但调用到 node_modules 组件时,我们无法修改其 dom 结构,怎么让这个参数强制透传呢?所以必须使用 context 对所有需要国际化的组件注入 props,而这个注入变量由顶层 Provider 控制。比如 antd local-provider。
然而共享一个 context 可能会冲突啊,现在你创建你的,我创建我的,咱们都互不影响,未来数据流框架大家会用的更爽,甚至一个项目可以同时并存多套数据流框架,因为互不影响嘛。
然而新的 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>
}
}
讨论地址是:精读《React's new Context API》 · Issue #64 · dt-fe/weekly