簡介
從Android的角度來理解:Context是一個場景,描述的是一個應用程式環境的資訊,即上下文,代表與作業系統的互動的一種過程。 React的context就是一個全域性變數,可以從根元件跨級別在React的元件中傳遞。
React context使用了Provider和Customer模式,和react-redux的模式非常像。在頂層的Provider中傳入value, 在子孫級的Consumer中獲取該值,並且能夠傳遞函式,用來修改context,多用於子孫級別的元件通訊
元件的通訊
props
最常見的父子傳值,如果要實現跨多代傳值,有點麻煩,需一層一層傳遞下去
context
Context 提供了一種方式,能夠讓資料在元件樹中傳遞時不必一級一級的手動傳遞
狀態管理工具(redux等)
例項
類元件
//建立Context元件
const ThemeContext = React.createContext({
theme: 'dark',
change: () => {}, //向上下文設定一個回撥方法
});
//執行APP
class App extends React.Component {
constructor(props) {
super(props);
this.change = () => { //change,會作為context引數傳遞
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
this.state = {
theme: themes.light,
change: this.change,
};
}
render() {
return (
<ThemeContext.Provider value={this.state}> //state包含了change方法
<Content />
</ThemeContext.Provider>
);
}
}
//中間元件
function Content() {
return (
<div>
<Button />
</div>
);
}
//接收元件
function Button() {
return (
<ThemeContext.Consumer>
{({theme, change}) => (
<button
onClick={change} //呼叫回撥
style={{backgroundColor: theme}}>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}
複製程式碼
使用多個context
import React, { createContext } from 'react';
// 建立Context的唯一方法
const ThemeContext = createContext()
const SizeContext = createContext()
class App extends React.Component {
state = {
theme: 'red',
size: 'small'
}
render () {
const { theme, size } = this.state
return (
// 使用 Context.Provider 包裹後續元件,value 指定值
<ThemeContext.Provider value={theme}>
{/* 當出現多個Context的時候,只需要將Context.Provider 巢狀即可 */}
<SizeContext.Provider value={size}>
{/* 當Context的Provider值更改時,Consumer 的值必須重新渲染 */}
<button onClick={() => {this.setState({ theme: 'yellow', size: 'big'})}}>按鈕</button>
<Middle></Middle>
</SizeContext.Provider>
</ThemeContext.Provider>
)
}
}
class Middle extends React.Component {
render () {
return <Bottom></Bottom>
}
}
class Bottom extends React.Component {
render () {
return (
// Context.Consumer Consumer消費者使用Context得值
// 但子元件不能是其他元件,必須渲染一個函式,函式的引數就是Context得值
// 當出現 多個Consumer的時候,進行巢狀,每個Consumer 的子元件必須是一個函式,即可
<ThemeContext.Consumer>
{
theme => (
<SizeContext.Consumer>
{
size => (<h1>ThemeContext 的 值為 {theme}; SizeContext 的值為 {size}</h1>)
}
</SizeContext.Consumer>
)
}
</ThemeContext.Consumer>
)
}
}
export default App;
複製程式碼
contextType
官方定義:掛載在 class 上的 contextType 屬性會被重賦值為一個由 React.createContext() 建立的 Context 物件。這能讓你使用 this.context 來消費最近 Context 上的那個值。你可以在任何生命週期中訪問到它,包括 render 函式中。
實際作用:contextType 可以簡化 context 的使用,不使用 consumer 也可以共享變數
import React, { createContext } from 'react';
const ThemeContext = createContext()
const SizeContext = createContext()
class App extends React.Component {
state = {
theme: 'red',
size: 'small'
}
render () {
const { theme, size } = this.state
return (
// 使用 Context.Provider 包裹後續元件,value 指定值
<ThemeContext.Provider value={theme}>
{/* 當出現多個Context的時候,只需要將Context.Provider 巢狀即可 */}
<SizeContext.Provider value={size}>
{/* 當Context的Provider值更改時,Consumer 的值必須重新渲染 */}
<button onClick={() => {this.setState({ theme: 'yellow', size: 'big'})}}>按鈕</button>
<Middle></Middle>
</SizeContext.Provider>
</ThemeContext.Provider>
)
}
}
class Middle extends React.Component {
render () {
return <Bottom></Bottom>
}
}
class Bottom extends React.Component {
// 申明靜態變數、contextType 將 context 直接賦值於 contextType
static contextType = ThemeContext
render () {
// 在 render 函式中 可以直接 訪問 this.context 獲取共享變數、這樣就可以不使用 consumer
const theme = this.context
return (
// Context.Consumer Consumer消費者使用Context得值
// 但子元件不能是其他元件,必須渲染一個函式,函式的引數就是Context得值
// 當出現 多個Consumer的時候,進行巢狀,每個Consumer 的子元件必須是一個函式,即可
<div>
<h1>ThemeContext 的 值為 {theme} </h1>
</div>
)
}
}
export default App;
複製程式碼
注意: contextType 只能在類元件中使用 ? 注意下面在函式元件使用的案例
一個元件如果有多個 consumer , contextType 只對其中一個有效,所以說,contextType 只能有一個
Context 缺點
網路說法:
-
在元件樹中,如果中間某一個元件 ShouldComponentUpdate returning false 了,會阻礙 context 的正常傳值,導致子元件無法獲取更新。 官方文件:
-
當 Provider 的 value 值發生變化時,它內部的所有消費元件都會重新渲染。Provider 及其內部 consumer 元件都不受制於 shouldComponentUpdate 函式,因此當 consumer 元件在其祖先元件退出更新的情況下也能更新。
-
元件本身 extends React.PureComponent 也會阻礙 context 的更新。
-
沒有redux好用
警告
注意:context類似於全域性變數做法,會讓元件失去獨立性、複用起來更困難,不能濫用、但本身它一定有適合使用的場景,具體看情況使用
引申
Component和PureComponent
React.PureComponent 與 React.Component 幾乎完全相同,但 React.PureComponent 通過prop和state的淺對比來實現 shouldComponentUpate()。 PureComponent 裡面也有一個內建的shouldComponentUpate.還要有immutable 對資料的管理不然會有BUG
如果React元件的 render() 函式在給定相同的props和state下渲染為相同的結果,在某些場景下你可以使用 React.PureComponent 來提升效能。
React.PureComponent 的 shouldComponentUpdate() 只會對物件進行淺對比。如果物件包含複雜的資料結構,它可能會因深層的資料不一致而產生錯誤的否定判斷(表現為物件深層的資料已改變檢視卻沒有更新, 原文:false-negatives)。當你期望只擁有簡單的props和state時,才去繼承 PureComponent ,或者在你知道深層的資料結構已經發生改變時使用 forceUpate() 。或者,考慮使用 不可變物件 來促進巢狀資料的快速比較。
無狀態函式式元件
常規函式寫法
function Item(props) {
return (
<div className='item'>
{props.title}
<span
className='deleteItem'
onClick={props.remove(props.id)}
> x </span>
</div>
)
}
複製程式碼
無生命週期方法
函式式元件,有時也被稱為無狀態元件,沒有任何生命週期方法,意味著每次上層元件樹狀態發生變更時它們都會重新渲染,這就是因為缺少 shouldComponentUpdate 方法導致的。這也同樣意味著您不能定義某些基於元件掛載和解除安裝的行為。
沒有 this 和 ref
在函式式元件中既不能使用 this 關鍵字或訪問到 ref。
如果您將 context 定義為函式的一個 props
function D(props, context) {
return (
<div>{this.context.user.name}</div>
);
}
D.contextTypes = {
user: React.PropTypes.object.isRequired
}
複製程式碼
優勢?
通過將邏輯和資料處理與 UI 展示剝離開來,我們就可以避免在展示型元件中處理任何的狀態。無狀態函式式元件強制實施了這樣的風格,因為您無論如何都沒有辦法處理本地狀態了。它強制您將任何的狀態處理移交至上層的元件樹,而讓下層的元件只做它們所做的——關注 UI 的展示。
容器型元件 (container component):
1、主要關注元件資料如何互動
2、擁有自身的state,從伺服器獲取資料,或與redux等其他資料處理模組協作
3、需要通過類定義元件宣告,幷包含生命週期函式和其他附加方法
展示型元件 (presentational component):
1、主要負責元件內容如何展示
2、從props接收父元件傳遞來的資料
3、大多數情況可以通過函式定義元件宣告
沒有邏輯意味著相同的表示具有相同的資料。 1、解耦了介面和資料的邏輯
2、更好的可複用性,比如同一個回覆列表展示元件可以套用不同資料來源的容器元件
3、利於團隊協作,一個人負責介面結構,一個人負責資料互動
缺陷
......