原文地址react
中文翻译git
翻译水平有限,部份内容比较晦涩,所以可能错误较多,请理解或指教。github
介绍用于解决现有局限性的全新Context API。缓存
type Theme = 'light' | 'dark';
// Pass a default theme to ensure type correctness
//传递默认的主题确保类型的正确
const ThemeContext: Context<Theme> = React.createContext('light');
class ThemeToggler extends React.Component {
state = {theme: 'light'};
render() {
return (
/*传递现有的Context的值给Provider的props上的`value`属性
数据变化使用Object.is来进行严格比较*/
<ThemeContext.Provider value={this.state.theme}>
<button
onClick={() =>
this.setState(state => ({
theme: state.theme === 'light' ? 'dark' : 'light',
}))
}>
Toggle theme
</button>
{this.props.children}
</ThemeContext.Provider>
);
}
}
class Title extends React.Component {
render() {
return (
//消费层使用一个渲染的prop API,以免与props命名空间冲突
<ThemeContext.Consumer>
{theme => (
<h1 style={{color: theme === 'light' ? '#000' : '#fff'}}>
{this.props.children}
</h1>
)}
</ThemeContext.Consumer>
);
}
}
复制代码
一般状况下,React里面的数据是按照top-down(parent to child)的顺序,经过props来传递的。但有些时候,跳过多个抽象层级来传递一些值每每是颇有用处的。就如例子中所传递的UI主题参数同样。不少组件都依赖于此,可你却不想在每一层组件上都使用prop来向下传递。安全
React里面的Context API正是为了解决这一问题所诞生的。在本文中,咱们将祖先组件成为Provider(提供者),把孩子组件称为Consumer(消费者)。markdown
如今的Context API的主要问题就是如何与shouldComponentUpdate
交互。若是一个中间组件使用了shouldComponentUpdate
来操做,那么它的下方组件在没有等待更新的时候,React将认为整个子树都没有发生改变。若是子树包含了Context的消费者,那么消费者将不会收到任何最新的Context。换句话来讲,Context的更改将不会在shouldComponentUpdate
返回为false上的组件上传播。数据结构
在React应用中,shouldComponentUpdate
是常用的优化操做方式。在共享组件与开源库中它的使用每每很频繁。在实践中,这意味着Context在广播变化的时候是不可靠的。框架
如今,开发者们经过使用订阅来绕过了shouldComponentUpdate
的问题异步
setState
来更新并触发重渲染。订阅被开源软件广发使用,例如Redux
和React Broadcast
。它颇有用,但它也有一些明显的缺点:介绍全新的组件类型:Provider
和Consumer
:ide
type Provider<T> = React.Component<{ value: T, children?: React.Node, }>; type Consumer<T> = React.Component<{ children: (value: T) => React.Node, }>; 复制代码
Provider
和Comsumer
是成对出现的,对于每个Provider
,都会有一个对应的Consumer
。一个Provider
-Consumer
组合是用React.createContext()
来产生的:
type Context<T> = { Provider: Provider<T>, Consumer: Consumer<T>, }; interface React { createContext<T>(defaultValue: T): Context<T>; } 复制代码
createContext
须要一个默认值来确保类型的正确。
请注意,即便任何提供者与任何消费者的值的类型相同,它们也不能联合使用。它们必须是同一个createContext
产生的结果。
Provider
在props
上接收一个value
,不管嵌套的深度如何,它都能被提供者的任何匹配的消费者所访问。
render() { return ( <Provider value={this.state.contextValue}> {this.props.children} </Provider> ); } 复制代码
为了更新Context的值,父级从新渲染并传递一个不一样的值。Context的变化将被检测到,检测所使用的是Object.is
来比较的。这意味着鼓励使用不可变或持久的数据结构。在典型的场景中,经过调用提供者父级的setState
来更新Context。
消费者使用一个render prop API
:
render() { return ( <Consumer> {contextValue => <Child arbitraryProp={contextValue} />} </Consumer> ) } 复制代码
请注意上面这个例子,Context的值能够传递给子组件上的任意prop。render prop API
的优势就是避免了破坏prop的命名空间。
若是一个Consumer
没有提供一个匹配的Provider
做为它的祖先,它会接收搭配传递给createContext
的默认值,确保类型安全。
该提案使用严格(参考)比较来检测对Context的更改。这部分鼓励使用不可变性或持久的数据结构。但许多常见的数据源都依赖与突变。例如Flux
的某些实现,甚至像Relay Modern
这样的更新的库。
可是,与异步渲染结合时,突变存在固有的问题,主要与撕裂有关。 对于依赖变异的体系结构,开发人员要么决定某种程度的撕裂是能够接受的,要么演变为更好地支持异步。 不管如何,这些问题并不只限于Context API。(From Google Translation)
一些依赖于突变的库的一个技巧就是克隆产生一个全新的外部容器(或者甚至只是在他们之间交替)。React将检测到一个新对象的引用而且触发一个更改。
建议的API只容许消费者从单一提供者类型读取值,这与当前的API不一样,后者容许消费者从任意数量的提供者类型读取。
解决方法是使用合成消费者(待定):
<FooConsumer> {foo => ( <BarConsumer> {bar => ( // Render using both foo and bar <Child foo={foo} bar={bar} /> )} </BarConsumer> )} </FooConsumer>; 复制代码
大多数围绕上下文的抽象已经使用了相似的模式。
咱们可使用像setState
同样工做的setContext
API,而不是依赖于引用相等来检测对上下文的更改。 可是,若是不考虑实现的开销,这个API只有在与突变结合使用时才有价值,咱们专门致力于阻止这种突变。
一个说法是,咱们能够经过将context做为参数传递给该方法来避免shouldComponentUpdate问题,将传入的Context与前一个Context进行比较,若是它们不一样,则返回true。 问题是,与prop或state不一样,咱们没有类型信息。 上下文对象的类型取决于组件在React树中的位置。 您能够对这两个对象执行浅层比较,但只有在咱们假定这些值是不可变的时才有效。 若是咱们假设这些值是不可变的,那么React可能会自动进行比较
咱们可使用咱们如今使用的基于类的API,而不是render prop:
class ThemeToggler extends React.Component { state = {theme: 'light'}; getChildContext() { return this.state.theme; } render() { return ( <> <button onClick={() => this.setState(state => ({ theme: state.theme === 'light' ? 'dark' : 'light', })) }> Toggle theme </button> {this.props.children} </> ); } } class Title extends React.Component { static contextType = ThemeContext; componentDidUpdate(prevProps, prevState, prevContext) { if (this.context !== prevContext) { alert('Theme changed!'); } } render() { return ( <h1 style={{color: this.context.theme === 'light' ? '#000' : '#fff'}}> {this.props.children} </h1> ); } } 复制代码
这个API的优势是您能够更轻松地访问生命周期方法中的上下文,可能避免在树中须要额外的组件。
可是,尽管增长React树的深度会带来一些开销,但使用特殊组件类型的优点在于,为消费者扫描树会更快,由于咱们能够快速跳过其余类型。 使用基于类的API时,咱们必须检查每一个类组件,它稍微慢一些。 这足以抵消额外组件的成本。
与类API不一样,render prop API还具备与现有Context API充分不一样的优点,咱们能够在过渡时期支持这两个版本,而不会形成太多混淆。
(注意,原文部分接下来的一些章节并未翻译,这些章节涉及的内容是React官方的一些计划)
对于依赖默认Context的值,这是颇有效的。在不少状况下,这种错误应该是开发者所形成的,所以咱们能够打印警告出来。为了关闭这个,你能够传递true给allowDetached
。
render() {
return (
// If there's no provider, this renders with the default theme.
// `allowDetached` suppresses the development warning
<ThemeContext.Consumer allowDetached={true}>
{theme => (
<h1 style={{color: theme === 'light' ? '#000' : '#fff'}}>
{this.props.children}
</h1>
)}
</ThemeContext.Consumer>
);
}
复制代码
对于警告和React DevTools,若是提供者和消费者具备displayName,则会有所帮助。 问题是这是否应该要求。 咱们可使其成为可选项,并使用Babel变换自动添加名称。 这是咱们用于createClass的策略。
children
做为prop或命名prop?