React’s ⚛️ new Context API
这是一种更人性化的方式,不再是“实验性功能”,并且现在它是一级API。并且使用了 render prop
!
你之前听说过 react 的 context API 吗?如果你听说过,你是否和其他用户一样犹豫是否直接使用它,因为你会在官方文档看到如下内容:
搜索的第一个内容就是“为什么不要去使用 context”。这不能激发使用 context API 的信心。为了考虑风险,部分提到:
如果想让你的应用更稳定,那不要使用 context。这是一个实验性的 API,在未来发布的 react 版本中可能会被移除。
所以,为什么会想要使用 context 呢?
你是否经历过将 state 从 react 树的根组件一直传递至叶节点的痛苦呢?这种痛苦被称为“prop drilling”,并且非常烦人。你必须让不关心该状态的组件传递 props 给关心该数据的子组件。特别是当你移动组件时,这种痛苦感更强烈了。
你当然可以使用标准 javascript 模块来避免这个问题。只需要将数据放到一个单例模块中,它可以在任何地方被访问或导入。但你可能在更新数据的时候遇到一些问题(你必须实现一个事件广播让订阅者知道何时更新了数据),并且服务端渲染也会对单例造成困扰。
所以,像 redux 这类的状态管理库就应运而生了。它允许你在 react 树的任何地方获取数据。你所要做的事只是使用一个叫
的东西,然后你的组件就可以通过 connected
神奇般地访问你的 store 数据。
如果我告诉你
使用了 context
这个实验性功能会怎么样?😱 这是真的!provider
组件将数据放在 context ,然后 connect
高阶组件从 context 读取数据。所以实际上, redux 并没有实现在任何地方访问数据… 是 context 做了这件事!
那么,现在知道为什么要使用 context 了吗?好吧。你可能已经知道并爱上它了!就算你没有直接使用 context, 你也可能通过 react-redux, MobX-react, react-router, glamorous 等间接地使用了它!
Context 重生
尽管我们现在很喜欢 context
, 但还记得那句“它很可能在将来的 react 版本中被移除”这句话吗?来吧,你会爱上它的!
一个多月以前, react 团队受到 yarn
, Rust
, Ember
的启发创建了 RFCs 项目。这个项目的第一个 PR 来自 Andrew Clark(react 核心团队成员)并称之为“新版 context”。在这个提交中,Andrew 列出了新版 context 要实现的功能。这里面有一些有趣的讨论。几天后,Andrew 开了一个新 PR “新 context API”。
所以,它看起来像什么?它比旧的 context API 要直观一百万倍。这是我想到的最简单有用的例子:
这是最简单的版本:
const ThemeContext = React.createContext('light')
class ThemeProvider extends React.Component {
state = {theme: 'light'}
render() {
return (
<ThemeContext.Provider value={this.state.theme}>
{this.props.children}
ThemeContext.Provider>
)
}
}
class App extends React.Component {
render() {
<ThemeProvider>
<ThemeContext.Consumer>
{val => <div>{val}div>}
ThemeContext.Consumer>
ThemeProvider>
}
}
你可能注意到我在消费组件中使用了 render props
(最佳实践!),但如果这不是你的菜,你也可以很容易地使用高阶组件或用了 context API 的其他方法去实现(这就是为什么它是最佳的)。
新的 context API 由以下几部分组件:
React.createContext
传递初始值(可选的使用位掩码功能)。这返回了一个包含Provider
和Consumer
的对象。Provider
在组件树中使用频率更高,并且接受一个 value 属性(可以是任何值)。Consumer
组件可以在 Provider 组件树下的任何地方使用,并接受一个“children”属性, children 必须是一个接受 value 值并返回 react 元素(JSX)的函数。
看到这个API 我异常地兴奋。react 团队将会移除 context 是一个实验性功能的警告,因为它现在是这个框架的一等功能。这意味着开发者有希望使用 context 简单地解决 prop-drilling 问题,而不必借助 redux 这类工具来解决这个痛点,也能更好更自由地使用简单的 react 。(又或者, James Kyle未发布的的解决方案是我们所期待的)
Context 实践
关于这个新的 context API,我遇到的比较多的问题是(或者其他的 props render模式),如何组合 providers 和 consumers 组件。当你将一堆的 render prop 组件放在一起时,就会变成 嵌套:
I mean come on (screen shot of actual code I’m playing with right now) pic.twitter.com/Ucc8gaxPMp
— Andrew Clark (@acdlite) January 24, 2018
那我们如何避免这个问题?如果这让你很烦恼,那你可以像解决常规的 javascript 问题一样:使用实用函数/组件。这有个例子:
const ThemeContext = React.createContext('light')
class ThemeProvider extends React.Component {/* code */}
const ThemeConsumer = ThemeContext.Consumer
const LanguageContext = React.createContext('en')
class LanguageProvider extends React.Component {/* code */}
const LanguageConsumer = LanguageContext.Consumer
function AppProviders({children}) {
return (
<LanguageProvider>
<ThemeProvider>
{children}
ThemeProvider>
LanguageProvider>
)
}
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>
}
}
上面举例了一个常见的案例,并使用特殊的函数或组件去让这些案例更人性化。就像你平时处理问题一样,举对例子了吗?我希望有用 😅
总结
就像我上面说的,我对这个 API 非常感兴趣。它现在还未发布,但将发布在下一个版本的 react。不要担心,旧的 context API 将支持到下一个主版本号发布。所以每个人都会有足够的时间迁移。不要忘了,react 团队有超过 50,000 react 组件,很可能会发布一个 codemod 来自动升级大家的代码。