首先欢迎你们关注个人Github博客,也算是对个人一点鼓励,毕竟写东西无法得到变现,能坚持下去也是靠的是本身的热情和你们的鼓励,但愿你们多多关注呀!很久已经没写React,发现连Context都发生了变化,突然有一种村里刚通上的网的感受,可能文章所说起的知识点已经算是过期了,仅仅算做是本身的学习体验吧,javascript
对于React开发者而言,Context应该是一个不陌生的概念,可是在16.3以前,React官方一直不推荐使用,并声称该特性属于实验性质的API,可能会从以后的版本中移除。可是在实践中很是多的第三方库都基于该特性,例如:react-redux、mobx-react。java
如上面的组件树中,A组件与B组件之间隔着很是多的组件,假如A组件但愿传递给B组件一个属性,那么不得不使用props将属性从A组件历经一系列中间组件最终跋山涉水传递给B组件。这样代码不只很是的麻烦,更重要的是中间的组件可能压根就用不上这个属性,却要承担一个传递的职责,这是咱们不但愿看见的。Context出现的目的就是为了解决这种场景,使得咱们能够直接将属性从A组件传递给B组件。react
这里所说的老版本Context指的是React16.3以前的版本所提供的Context属性,在我看来,这种Context是以一种协商声明的方式使用的。做为属性提供者(Provider)须要显式声明哪些属性能够被跨层级访问而且须要声明这些属性的类型。而做为属性的使用者(Consumer)也须要显式声明要这些属性的类型。官方文档中给出了下面的例子:git
import React, {Component} from 'react'; import PropTypes from 'prop-types'; class Button extends React.Component { static contextTypes = { color: PropTypes.string }; render() { return ( <button style={{background: this.context.color}}> {this.props.children} </button> ); } } class Message extends React.Component { render() { return ( <div> {this.props.text} <Button>Delete</Button> </div> ); } } class MessageList extends React.Component { static childContextTypes = { color: PropTypes.string }; getChildContext() { return {color: "red"}; } render() { const children = this.props.messages.map((message) => <Message text={message.text} /> ); return <div>{children}</div>; } }
咱们能够看到MessageList
经过函数getChildContext
显式声明提供color
属性,而且经过静态属性childContextTypes
声明了该属性的类型。而Button
经过静态属性contextTypes
声明了要使用属性的类型,两者经过协商的方式约定了跨层级传递属性的信息。Context确实很是方便的解决了跨层级传递属性的状况,可是为何官方却不推荐使用呢?github
首先Context
的使用是与React可复用组件的逻辑背道而驰的,在React的思惟中,全部组件应该具备复用的特性,可是正是由于Context的引入,组件复用的使用变得严格起来。就以上面的代码为例,若是想要复用Button
组件,必须在上层组件中含有一个能够提供String
类型的colorContext
,因此复用要求变得严格起来。而且更重要的是,当你尝试修改Context的值时,可能会触发不肯定的状态。咱们举一个例子,咱们将上面的MessageList
稍做改造,使得Context内容能够动态改变:redux
class MessageList extends React.Component { state = { color: "red" }; static childContextTypes = { color: PropTypes.string }; getChildContext() { return {color: this.state.color}; } render() { const children = this.props.messages.map((message) => <Message text={message.text} /> ); return ( <div> <div>{children}</div> <button onClick={this._changeColor}>Change Color</button> </div> ); } _changeColor = () => { const colors = ["red", "green", "blue"]; const index = (colors.indexOf(this.state.color) + 1) % 3; this.setState({ color: colors[index] }); } }
上面的例子中咱们MessageList
组件Context提供的color
属性改为了state
的属性,当每次使用setState
刷新color
的时候,子组件也会被刷新,所以对应按钮的颜色也会发生改变,一切看起来是很是的完美。可是一旦组件间的组件存在生命周期函数ShouldComponentUpdate
那么一切就变得诡异起来。咱们知道PureComponent
实质就是利用ShouldComponentUpdate
避免没必要要的刷新的,所以咱们能够对以前的例子作一个小小的改造:安全
class Message extends React.PureComponent { render() { return ( <div> {this.props.text} <Button>Delete</Button> </div> ); } }
你会发现即便你在MessageList
中改变了Context
的值,也没法致使子组件中按钮的颜色刷新。这是由于Message
组件继承自PureComponent
,在没有接受到新的props
改变或者state
变化时生命周期函数shouldComponentUpdate
返回的是false
,所以Message
及其子组件并无刷新,致使Button
组件没有刷新到最新的颜色。ide
若是你的Context值是不会改变的,或者只是在组件初始化的时候才会使用一次,那么一切问题都不会存在。可是若是须要改变Context的状况下,如何安全使用呢? Michel Weststrate在[How to safely use React context
](https://medium.com/@mweststra...。做者认为咱们不该该直接在getChildContext
中直接返回state属性,而是应该像依赖注入(DI)同样使用conext。函数
class Theme { constructor(color) { this.color = color this.subscriptions = [] } setColor(color) { this.color = color this.subscriptions.forEach(f => f()) } subscribe(f) { this.subscriptions.push(f) } } class Button extends React.Component { static contextTypes = { theme: PropTypes.Object }; componentDidMount() { this.context.theme.subscribe(() => this.forceUpdate()); } render() { return ( <button style={{background: this.context.theme.color}}> {this.props.children} </button> ); } } class MessageList extends React.Component { constructor(props){ super(props); this.theme = new Theme("red"); } static childContextTypes = { theme: PropTypes.Object }; getChildContext() { return { theme: this.theme }; } render() { const children = this.props.messages.map((message) => <Message text={message.text} /> ); return ( <div> <div>{children}</div> <button onClick={this._changeColor}>Change Color</button> </div> ); } _changeColor = () => { const colors = ["red", "green", "blue"]; const index = (colors.indexOf(this.theme.color) + 1) % 3; this.theme.setColor(colors[index]); } }
在上面的例子中咱们创造了一个Theme
类用来管理样式,而后经过Context
将Theme
的实例向下传递,在Button
中获取到该实例而且订阅样式变化,在样式变化时调用forceUpdate
强制刷新达到刷新界面的目的。固然上面的例子只是一个雏形,具体使用时还须要考虑到其余的方面内容,例如在组件销毁时须要取消监听等方面。学习
回顾一下以前版本的Context,配置起来仍是比较麻烦的,尤为还须要在对应的两个组件中分别使用childContextTypes
和contextTypes
的声明Context属性的类型。并且其实这两个类型声明并不能很好的约束context
。举一个例子,假设分别有三个组件: GrandFather、Father、Son,渲染顺序分别是:
GrandFather -> Father -> Son
那么假设说组件GrandFather提供的context是类型为number
键为value
的值1,而Father提供也是类型为number
的键为value
的值2,组件Son声明得到的是类型为number
的键为value
的context,咱们确定知道组件Son中this.context.value
值为2,由于context在遇到同名Key值时确定取的是最靠近的父组件。
一样地咱们假设件GrandFather提供的context是类型为string
键为value
的值"1",而Father提供是类型为number
的键为value
的值2,组件Son声明得到的是类型为string
的键为value
的context,那么组件Son会取到GrandFather的context值吗?事实上并不会,仍然取到的值是2,只不过在开发过程环境下会输出:
Invalid contextvalue
of typenumber
supplied toSon
, expectedstring
所以咱们能得出静态属性childContextTypes
和contextTypes
只能提供开发的辅助性做用,对实际的context取值并不能起到约束性的做用,即便这样咱们也不得不重复体力劳动,一遍遍的声明childContextTypes
和contextTypes
属性。
新的Context发布于React 16.3版本,相比于以前组件内部协商声明的方式,新版本下的Context大不相同,采用了声明式的写法,经过render props的方式获取Context,不会受到生命周期shouldComponentUpdate
的影响。上面的例子用新的Context改写为:
import React, {Component} from 'react'; const ThemeContext = React.createContext({ theme: 'red'}); class Button extends React.Component { render(){ return( <ThemeContext.Consumer> {({color}) => { return ( <button style={{background: color}}> {this.props.children} </button> ); }} </ThemeContext.Consumer> ); } } class Message extends React.PureComponent { render() { return ( <div> {this.props.text} <Button>Delete</Button> </div> ); } } class MessageList extends React.Component { state = { theme: { color: "red" } }; render() { return ( <ThemeContext.Provider value={this.state.theme}> <div> {this.props.messages.map((message) => <Message text={message.text}/>)} <button onClick={this._changeColor}>Change Color</button> </div> </ThemeContext.Provider> ) } _changeColor = () => { const colors = ["red", "green", "blue"]; const index = (colors.indexOf(this.state.theme.color) + 1) % 3; this.setState({ theme: { color: colors[index] } }); } }
咱们能够看到新的Context使用React.createContext
的方式建立了一个Context
实例,而后经过Provider
的方式提供Context值,而经过Consumer
配合render props的方式获取到Context值,即便中间组件中存在shouldComponentUpdate
返回false
,也不会致使Context没法刷新的问题,解决了以前存在的问题。咱们看到在调用React.createContext
建立Context
实例的时候,咱们传入了一个默认的Context
值,该值仅会在Consumer
在组件树中没法找到匹配的Provider
才会使用,所以即便你给Provider
的value
传入undefined
值时,Consumer
也不会使用默认值。
新版的Context API相比于以前的Context API更符合React的思想,而且能解决componentShouldUpdate
的带来的问题。与此同时你的项目须要增长专门的文件来建立Context
。在 React v17 中,可能就会删除对老版 Context API 的支持,因此仍是须要尽快升级。最后讲了这么多,可是在项目中仍是要尽可能避免Context的滥用,不然会形成组件间依赖过于复杂。