前言
React作為前端最?的框架之一,但是有的時候我們僅限於能用的階段,有一些高階用法,我們在日常開發中卻很少涉足。但是一旦用起來,我們就能發現它的方便和強大之處,我們就會越來越發現我們已經離不開它了!這就像是剛用React時,我內心是拒絕的,但是現在我已經離不開它了,越來越不能理解以前自己為什麼抱著JQuery不放呢!
今天我們重點講一下Context這個高階API,以及如何封裝它,讓它更加易用!
Context簡介
Context 通過元件樹提供了一個傳遞資料的方法,從而避免了在每一個層級手動的傳遞 props 屬性。
在一個典型的 React 應用中,資料是通過props屬性由上向下(由父及子)的進行傳遞的,但這對於某些型別的屬性而言是極其繁瑣的(例如:地區偏好,UI主題),這是應用程式中許多元件都所需要的。 Context 提供了一種在元件之間共享此類值的方式,而不必通過元件樹的每個層級顯式地傳遞 props 。
簡單說就是,當你不想在元件樹中通過逐層傳遞props或者state的方式來傳遞資料時,可以使用Context來實現跨層級的元件資料傳遞。
假設我們有一種場景,我們有一個業務容器App,裡面有一個元件容器Container,Container元件內包含一個Form表單,Form表單裡面有一個提交按鈕SubmitButton。假如使用props傳遞,我們就不得不傳遞四層。
看到了嗎?很方便吧!這裡我們使用Context,在元件可以直接通過context獲取最頂層繫結的值,避免了一層層傳遞props的麻煩,也減少出錯的可能性。
如何使用Context
如果要Context發揮作用,需要用到兩種元件,一個是Context生產者(Provider),通常是一個父節點,另外是一個Context的消費者(Consumer),通常是一個或者多個子節點。所以Context的使用基於生產者消費者模式。
Context 設計目的是為共享那些被認為對於一個元件樹而言是“全域性”的資料,例如當前認證的使用者、主題或首選語言。例如,在下面的程式碼中,我們通過一個“theme”屬性手動調整一個按鈕元件的樣式,使用context,我們可以避免通過中間元素傳遞props。
// 建立一個 theme Context, 預設 theme 的值為 light
const ThemeContext = React.createContext('light');
function ThemedButton(props) {
// ThemedButton 元件從 context 接收 theme
return (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
}
// 中間元件
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
複製程式碼
一種更簡單的使用方式
看了上面的使用方式,有沒有覺得還是有一些不爽,有沒有簡單一點的方式呢,或者能不能幫我封裝一下呢? 當然可以,Javascript工程師是無所不能的!!!
首先是我們的provider.js,這個就是我們封裝的context使用工具
import React, { Component } from 'react';
export const Context = React.createContext();
export class ContextProvider extends Component {
render() {
return (
<Context.Provider value={this.props.context}>
{this.props.children}
</Context.Provider>
);
}
}
/**
* 用註解的方式給子元件注入屬性
*/
export const injectContext = (contexts) => RealComponent => {
return class extends Component {
render() {
return (
<Context.Consumer>
{context => {
// 將頂層的context分發到各層
let mapContext = {};
if(Array.isArray(contexts)) {
contexts.map(item => {
mapContext[item] = context[item];
});
}
return (
<RealComponent {...mapContext} {...this.props} />
)
}}
</Context.Consumer>
);
}
};
};
複製程式碼
還是舉個例子,來讓大家明白上述封裝的方法的方便之處。 假如要實現GrandParent -> Parent -> Son,從GrandParent元件傳遞屬性到GrandSon元件,每個元件都有一個獨立的檔案。
先看入口檔案,我們在入口檔案進行繫結上下文,使用provider裡面的ContextProvider類,這裡我們主要繫結了propA和propB。
// 入口檔案
import React, { PureComponent } from 'react';
import { ContextProvider } from './provider';
import GrandParent from './GrandParent';
class Index extends PureComponent {
render () {
return (
<ContextProvider context={{
propA: 'propA',
propB: 'propB'
}}>
<GrandParent />
</ContextProvider>
)
}
}
複製程式碼
Parent元件沒什麼特殊的
import React, { PureComponent } from 'react';
import Son from './Son';
class Index extends PureComponent {
render () {
return (
<Son />
)
}
}
複製程式碼
Son元件是真正使用屬性propA和propB的地方,我們通過ES6的Decorator實現,非常方便。注入後,可以像props一樣使用。
import React, { PureComponent } from 'react';
import { injectContext } from './provider';
import Son from './Son';
@injextContext(['propA', 'propB'])
class Index extends PureComponent {
render () {
return (
<div>
<span>propA為{this.props.propA}</span>
<span>propB為{this.props.propB}</span>
</div>
)
}
}
複製程式碼
後記
在這一小節中,我們主要講了React的一個高階語法Context,而且為了使用方便,我們封裝了ContextProvider類和injextContext方法,使用時利用ES6的Decorator語法糖,非常簡便。大家在日常開發中,也可以封裝出一些這樣的小工具,可以極大提升開發效率。
最後元旦快到了,祝大家新年快樂!!!
@Author: WaterMan