你想知道的React組件設計模式這裏都有(下)


上一篇介紹了三種設計模式,包括:

  • 容器與展示組件;

  • 高階組件;

  • render props。

這篇我們繼續介紹三種設計模式,包括:

  • context模式 ;

  • 組合組件 ;

  • 繼承模式。

爲了更好的理解,你可以將相應源碼下載下來查看:https://github.com/imalextu/learn-react-patterns

一、Context模式


>>>>

概念介紹

React 的 Context 接口提供了一個無需爲每層組件手動添加 props ,就能在組件樹間進行數據傳遞的方法。

在一個典型的 React 應用中,數據是通過 props 屬性自上而下(由父及子)進行傳遞的,但這種做法對於某些類型的屬性而言是極其繁瑣的(例如:地區偏好,UI 主題),這些屬性是應用程序中許多組件都需要的。Context 提供了一種在組件之間共享此類值的方式,而不必顯式地通過組件樹的逐層傳遞 props。

>>>>

示例

React v16.3.0前後的Context相關API不同,這邊只介紹新版本的Context使用方法。

第一步:新建createContext

首先,要用新提供的 createContext 函數創造一個“上下文”對象。


 
const ThemeContext = React.createContext();

第二步:生成Provider 和 Consumer

接着,我們用ThemeContext生成兩個屬性,分別是Provider和Consumer。從字面意思即可理解。Provider供數據提供者使用,Consumer供數據消費者使用。


 
const ThemeProvider = ThemeContext.Provider;	
const ThemeConsumer = ThemeContext.Consumer;

第三步:使用ThemeProvider給數據提供者


 
const Context = () => {	
  return (	
    <div>	
      <ThemeProvider value={{ mainColor: 'blue', textColor: 'pink' }} >	
        <Page />	
      </ThemeProvider>	
    </div>	
  )	
}	
	
// 調用context	
const Page = () => (	
  <div>	
    <Title>標題</Title>	
    <Content>	
      內容	
    </Content>	
  </div>	
);

第四步:使用ThemeConsumer給數據接收者


 
// 這裏演示一個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即可。

640?wx_fmt=png

>>>>

使用注意事項

因爲 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需要高亮。

640?wx_fmt=png

如果我們使用常規寫法,用 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>	
  }	
}	

我們具體看下Provider組件是如何定義的。通過這段代碼props.children(allProps),我們調用了傳入的函數。


 
const Provider = (props) => {	
  // 判斷是否是女性用戶	
  let isWoman = Math.random() > 0.5 ? true : false	
  if (isWoman) {	
    const allProps = { add: '高階組件增加的屬性', ...props }	
    return props.children(allProps)	
  } else {	
    return <div>女士專用,男士無權瀏覽</div>;	
  }	
}

我們可以看到Mobile和Pc共享了Base的邏輯,實現了複用。‍>>>>

組合與繼承

如果你剛使用React,可能繼承的方式對大家來說更熟悉的。因爲繼承看起來很方便,也很好理解。但是React官方並不推薦使用繼承,因爲各種組合的模式完全足夠使用,上面的例子我們完全可以用組合的思想去實現。爲什麼不推薦使用繼承?繼承有兩個缺點,其一是,父類的屬性和方法,子類是無條件繼承的。也就是說,不管子類願意不願意,都必須繼承父類所有的屬性和方法,這樣就不夠靈活了。其二是,js中class並不直接支持多繼承。這兩個缺點使得繼承相對於組合組件缺少了靈活性以及可擴展性。請記住,組合優於繼承!組件的複用請第一時間想到使用組合而非繼承。

尾聲


到這裏,六種React組件設計模式就就講完了。這六種模式已經覆蓋了絕大多數的組件使用場景。隨着React的更新,也許將來會有更多組件設計模式出現。但是思想都是想通的,比如“責任分離”、“不要重複自己”(DRY,Don't Repeat Yourself) 等等。明白這些代碼設計思想,將來我們也能很快地掌握新的組件設計模式。
參考文檔:

  • React官方文檔

      (http://t.cn/AiYGz4Na)

  • React Component Patterns

      (http://t.cn/EvsJ8gj)

  • React實戰:設計模式和最佳實踐

      (http://t.cn/EUy09Ml)

  • Presentational and Container Components

     (http://t.cn/RqMyfwV)

  • React組件「設計模式」快速指南

    http://t.cn/AiThDOqG

  • 爲什麼老鳥要告訴你優先使用組合而不是繼承?

    http://t.cn/AiThD8E4


640?wx_fmt=png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章