上一篇介紹了三種設計模式,包括1.容器與展現組件 2.高階組件 3.render props。html
這篇咱們繼續介紹三種設計模式,包括1.context模式 2.高階組件 3.繼承模式java
爲了更好的理解,你能夠將相應源碼下載下來查看:源碼地址react
React 的 Context 接口提供了一個無需爲每層組件手動添加 props ,就能在組件樹間進行數據傳遞的方法。git
在一個典型的 React 應用中,數據是經過 props 屬性自上而下(由父及子)進行傳遞的,但這種作法對於某些類型的屬性而言是極其繁瑣的(例如:地區偏好,UI 主題),這些屬性是應用程序中許多組件都須要的。Context 提供了一種在組件之間共享此類值的方式,而沒必要顯式地經過組件樹的逐層傳遞 props。github
React v16.3.0先後的Context相關API不一樣,這邊只介紹新版本的Context使用方法。設計模式
首先,要用新提供的 createContext 函數創造一個「上下文」對象。bash
const ThemeContext = React.createContext();
複製代碼
接着,咱們用ThemeContext生成兩個屬性,分別是Provider和Consumer。從字面意思便可理解。Provider供數據提供者使用,Consumer供數據消費者使用。antd
const ThemeProvider = ThemeContext.Provider;
const ThemeConsumer = ThemeContext.Consumer;
複製代碼
const Context = () => {
return (
<div>
<ThemeProvider value={{ mainColor: 'blue', textColor: 'pink' }} >
<Page />
</ThemeProvider>
</div>
)
}
// 調用context
const Page = () => (
<div>
<Title>標題</Title>
<Content>
內容
</Content>
</div>
);
複製代碼
// 這裏演示一個class組件。Counsumer使用了renderProps模式哦。
class Title extends React.Component {
render() {
return (
<ThemeConsumer>
{
(theme) => (
<h1 style={{ color: theme.mainColor }}>
{this.props.children}
</h1>
)
}
</ThemeConsumer>
);
}
}
// 這裏演示一個函數式組件
const Content = (props, context) => {
return (
<ThemeConsumer>
{
(theme) => (
<p style={{ color: theme.textColor }}>
{props.children}
</p>
)
}
</ThemeConsumer>
);
};
複製代碼
Context 主要應用場景在於不少不一樣層級的組件須要訪問一樣一些的數據。以下圖,組件a、組件g、組件f須要共享數據,則只須要在最外層套上Provider,須要共享的組件使用Consumer便可。ide
由於 context 會使用參考標識(reference identity)來決定什麼時候進行渲染,這裏可能會有一些陷阱,當 provider 的父組件進行重渲染時,可能會在 consumers 組件中觸發意外的渲染。舉個例子,當每一次 Provider 重渲染時,如下的代碼會重渲染全部下面的 consumers 組件,由於 value 屬性老是被賦值爲新的對象:函數
class App extends React.Component {
render() {
return (
<Provider value={{something: 'something'}}>
<Toolbar />
</Provider>
);
}
}
複製代碼
爲了防止這種狀況,將 value 狀態提高到父節點的 state 裏:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'},
};
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}
複製代碼
Compound Component 翻譯爲組合組件。借用組合組件,使用者只須要傳遞子組件,子組件所須要的props在父組件會封裝好,引用子組件的時候就不必傳遞全部props了。
組合組件核心的兩個方法是React.Children.map和React.cloneElement。React.Children.map 用來遍歷得到組件的子元素。React.cloneElement 則用來複制元素,這個函數第一個參數就是被複制的元素,第二個參數能夠增長新產生元素的 props ,咱們就是利用這個函數,把想要的 props 傳入子元素。
咱們設計一個相似於 antd 中的 Tabs 組件,提供tab切換功能,而被選中的TabItem須要高亮。
若是咱們使用常規寫法,用 Tabs 中一個 state 記錄當前被選中的 Tabitem 序號,而後根據這個 state 傳遞 props 給 TabItem,還須要傳遞一個 onClick 事件進去,捕獲點擊選擇事件。
<TabItem active={true} onClick={this.onClick}>One</TabItem>
<TabItem active={false} onClick={this.onClick}>Two</TabItem>
<TabItem active={false} onClick={this.onClick}>Three</TabItem>
複製代碼
每次增長一個TabItem,是否是都須要傳遞active和onClick,這太繁瑣了!咱們用compound模式解決這個問題。
const TabItem = (props) => {
const {active, onClick} = props;
const tabStyle = {
'max-width': '150px',
color: active ? 'red' : 'green',
border: active ? '1px red solid' : '0px',
};
return (
<h1 style={tabStyle} onClick={onClick}>
{props.children}
</h1>
);
};
// jsx調用Tabs以及TabItem
const Compound = (props, context) => {
return (
<Tabs>
<TabItem>One</TabItem>
<TabItem>Two</TabItem>
<TabItem>Three</TabItem>
<TabItem>Four</TabItem>
</Tabs>
);
};
複製代碼
上面的代碼展現了咱們最終調用Tabs以及TabItem的樣子。重點在於Tabs咱們要如何實現:
class Tabs extends React.Component {
state = {
activeIndex: 0
}
render() {
const newChildren = React.Children.map(this.props.children, (child, index) => {
if (child.type) {
return React.cloneElement(child, {
active: this.state.activeIndex === index,
onClick: () => this.setState({activeIndex: index})
});
} else {
return child;
}
});
return (
<Fragment>
{newChildren}
</Fragment>
);
}
}
複製代碼
本來咱們要如此調用:
<TabItem active={false} onClick={this.onClick}>One</TabItem>
複製代碼
如今咱們這樣調用就能夠了:
<TabItem>One</TabItem>
複製代碼
經過組合使用React.Children.map和React.cloneElement,咱們讓TabItem得到了它想要的屬性,簡化了TabItem的使用,是否是很神奇!
組合組件設計模式通常應用在一些共享組件上。如 select 和 option , Tab 和TabItem 等,經過組合組件,使用者只須要傳遞子組件,子組件所須要的 props 在父組件會封裝好,引用子組件的時候就不必傳遞全部 props 了。
咱們能夠在共享的組件中運用這種模式,簡化組件使用者的調用方式,antd 當中你就能看到許多組合組件的使用。
說了那麼多的模式,咱們最後來談談很熟悉的繼承模式。若是組件定義爲class組件,那麼咱們固然可使用繼承的模式來實現組件的複用。
咱們經過一個基類來實現一些通用的邏輯,而後再經過繼承分別實現兩個子類。
class Base extends React.PureComponent {
getAlbumItem = () => {
return null
}
render () {
return (
<div style={{border:'1px solid red',margin:5,width:300}}>
{this.getAlbumItem()}
<div>通用邏輯寫這裏</div>
</div>
)
}
}
class Mobile extends Base {
getAlbumItem = () => {
return <span>mobile</span>
}
}
class Pc extends Base {
getAlbumItem = () => {
return <span>pc</span>
}
}
複製代碼
咱們能夠看到Mobile和Pc共享了Base的邏輯,實現了複用。
若是你剛使用React,可能繼承的方式對你們來講更熟悉的。由於繼承看起來很方便,也很好理解。可是React官方並不推薦使用繼承,由於各類組合的模式徹底足夠使用,上面的例子咱們徹底能夠用組合的思想去實現。
爲何不推薦使用繼承?繼承有兩個缺點,其一是,父類的屬性和方法,子類是無條件繼承的。也就是說,無論子類願意不肯意,都必須繼承父類全部的屬性和方法,這樣就不夠靈活了。其二是,js中class並不直接支持多繼承。這兩個缺點使得繼承相對於組合組件缺乏了靈活性以及可擴展性。
請記住,組合優於繼承!組件的複用請第一時間想到使用組合而非繼承。
到這裏,六種React組件設計模式就就講完了。這六種模式已經覆蓋了絕大多數的組件使用場景。隨着React的更新,也許未來會有更多組件設計模式出現。可是思想都是想通的,好比「責任分離」、「不要重複本身」(DRY,Don't Repeat Yourself) 等等。明白這些代碼設計思想,未來咱們也能很快地掌握新的組件設計模式。
參考連接: