首先歡迎你們關注個人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的濫用,不然會形成組件間依賴過於複雜。