前言
首先歡迎大家關注我的Github博客,也算是對我的一點鼓勵,畢竟寫東西沒法獲得變現,能堅持下去也是靠的是自己的熱情和大家的鼓勵,希望大家多多關注呀!好久已經沒寫React,發現連Context都發生了變化,忽然有一種村里剛通上的網的感覺,可能文章所提及的知識點已經算是過時了,僅僅算作是自己的學習體驗吧,
Context對于React開發者而言,Context應該是一個不陌生的概念,但是在16.3之前,React官方一直不推薦使用,并聲稱該特性屬于實驗性質的API,可能會從之后的版本中移除。但是在實踐中非常多的第三方庫都基于該特性,例如:react-redux、mobx-react。
如上面的組件樹中,A組件與B組件之間隔著非常多的組件,假如A組件希望傳遞給B組件一個屬性,那么不得不使用props將屬性從A組件歷經一系列中間組件最終跋山涉水傳遞給B組件。這樣代碼不僅非常的麻煩,更重要的是中間的組件可能壓根就用不上這個屬性,卻要承擔一個傳遞的職責,這是我們不希望看見的。Context出現的目的就是為了解決這種場景,使得我們可以直接將屬性從A組件傳遞給B組件。
Legacy Context這里所說的老版本Context指的是React16.3之前的版本所提供的Context屬性,在我看來,這種Context是以一種協商聲明的方式使用的。作為屬性提供者(Provider)需要顯式聲明哪些屬性可以被跨層級訪問并且需要聲明這些屬性的類型。而作為屬性的使用者(Consumer)也需要顯式聲明要這些屬性的類型。官方文檔中給出了下面的例子:
import React, {Component} from "react"; import PropTypes from "prop-types"; class Button extends React.Component { static contextTypes = { color: PropTypes.string }; render() { return ( ); } } class Message extends React.Component { render() { return ({this.props.text}); } } class MessageList extends React.Component { static childContextTypes = { color: PropTypes.string }; getChildContext() { return {color: "red"}; } render() { const children = this.props.messages.map((message) =>); return {children}; } }
我們可以看到MessageList通過函數getChildContext顯式聲明提供color屬性,并且通過靜態屬性childContextTypes聲明了該屬性的類型。而Button通過靜態屬性contextTypes聲明了要使用屬性的類型,二者通過協商的方式約定了跨層級傳遞屬性的信息。Context確實非常方便的解決了跨層級傳遞屬性的情況,但是為什么官方卻不推薦使用呢?
首先Context的使用是與React可復用組件的邏輯背道而馳的,在React的思維中,所有組件應該具有復用的特性,但是正是因為Context的引入,組件復用的使用變得嚴格起來。就以上面的代碼為例,如果想要復用Button組件,必須在上層組件中含有一個可以提供String類型的colorContext,所以復用要求變得嚴格起來。并且更重要的是,當你嘗試修改Context的值時,可能會觸發不確定的狀態。我們舉一個例子,我們將上面的MessageList稍作改造,使得Context內容可以動態改變:
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) =>); return ( ); } _changeColor = () => { const colors = ["red", "green", "blue"]; const index = (colors.indexOf(this.state.color) + 1) % 3; this.setState({ color: colors[index] }); } }{children}
上面的例子中我們MessageList組件Context提供的color屬性改成了state的屬性,當每次使用setState刷新color的時候,子組件也會被刷新,因此對應按鈕的顏色也會發生改變,一切看起來是非常的完美。但是一旦組件間的組件存在生命周期函數ShouldComponentUpdate那么一切就變得詭異起來。我們知道PureComponent實質就是利用ShouldComponentUpdate避免不必要的刷新的,因此我們可以對之前的例子做一個小小的改造:
class Message extends React.PureComponent { render() { return ({this.props.text}); } }
你會發現即使你在MessageList中改變了Context的值,也無法導致子組件中按鈕的顏色刷新。這是因為Message組件繼承自PureComponent,在沒有接受到新的props改變或者state變化時生命周期函數shouldComponentUpdate返回的是false,因此Message及其子組件并沒有刷新,導致Button組件沒有刷新到最新的顏色。
如果你的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 ( ); } } 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) =>); return ( ); } _changeColor = () => { const colors = ["red", "green", "blue"]; const index = (colors.indexOf(this.theme.color) + 1) % 3; this.theme.setColor(colors[index]); } }{children}
在上面的例子中我們創造了一個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 context value of type number supplied to Son, expected string
因此我們能得出靜態屬性childContextTypes和contextTypes只能提供開發的輔助性作用,對實際的context取值并不能起到約束性的作用,即使這樣我們也不得不重復體力勞動,一遍遍的聲明childContextTypes和contextTypes屬性。
New Context新的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({({color}) => { return ( ); }} ); } } class Message extends React.PureComponent { render() { return ({this.props.text}); } } class MessageList extends React.Component { state = { theme: { color: "red" } }; render() { return () } _changeColor = () => { const colors = ["red", "green", "blue"]; const index = (colors.indexOf(this.state.theme.color) + 1) % 3; this.setState({ theme: { color: colors[index] } }); } } {this.props.messages.map((message) =>)}
我們可以看到新的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的濫用,否則會造成組件間依賴過于復雜。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101146.html
摘要:然而之前的相當于從最頂層的組件開始,自頂向下遞歸調用,不會被中斷,這樣就會持續占用瀏覽器主線程。眾所周知,是單線程運行,長時間占用主線程會阻塞其他類似于樣式計算布局繪制等運算,從而出現掉幀的情況。 前言 首先歡迎大家關注我的Github博客,也算是對我的一點鼓勵,畢竟寫東西沒法獲得變現,能堅持下去也是靠的是自己的熱情和大家的鼓勵,希望大家多多關注呀!從今年年初離開React開發崗,...
摘要:前言我們知道在使用時,我們需要通過去創建實例,譬如為的配置文件那么我們看下方法的具體實現創建實例并執行解析主要通過執行對配置文件的解析,具體實現如下文配置文件解析解析標簽解析標簽解析別名標簽解析插件標簽解析標簽解析標簽解析標簽從的方法實現我 前言 我們知道在使用 Mybatis 時,我們需要通過 SqlSessionFactoryBuild 去創建 SqlSessionFactory ...
摘要:為何重拾使用了多年,但是對其底層的一些實現還是一知半解,一些概念比較模糊故決定重新拾起,加深對的認識。小結是在完成創建后對其進行后置處理的接口是在完成實例化對其進行的后置處理接口是框架底層的核心接口,其提供了創建,獲取等核心功能。 為何重拾 使用了 Spring 多年,但是對其底層的一些實現還是一知半解,一些概念比較模糊;故決定重新拾起,加深對 Spring 的認識。 重拾計劃 spr...
摘要:目錄結構說明集多編程范式之大成者,使開發者能夠快速的開發測試部署程序,支持全平臺靜態編譯。上目錄位置主要目錄包含如下圖,分別進行說明文件夾存放檢查器的輔助文件。工作區有個子目錄目錄目錄和目錄。目錄用于以代碼包的形式組織并保存源碼文件。 go 目錄結構說明 ??golang集多編程范式之大成者,使開發者能夠快速的開發、測試、部署程序,支持全平臺靜態編譯。go具有優秀的依賴管理,高效的運行...
閱讀 3064·2021-10-12 10:20
閱讀 2809·2021-09-27 13:56
閱讀 790·2021-09-27 13:36
閱讀 1424·2021-09-26 09:46
閱讀 2417·2019-08-30 14:02
閱讀 2685·2019-08-28 18:14
閱讀 1258·2019-08-26 10:32
閱讀 1700·2019-08-23 18:25