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

上一篇介紹了三種設計模式,包括1.容器與展現組件 2.高階組件 3.render props。html

這篇咱們繼續介紹三種設計模式,包括1.context模式 2.高階組件 3.繼承模式java

爲了更好的理解,你能夠將相應源碼下載下來查看:源碼地址react

Context模式

概念介紹

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

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

示例

React v16.3.0先後的Context相關API不一樣,這邊只介紹新版本的Context使用方法。設計模式

第一步:新建createContext

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

const ThemeContext = React.createContext();
複製代碼

第二步:生成Provider 和 Consumer

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

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便可。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

概念介紹

Compound Component 翻譯爲組合組件。借用組合組件,使用者只須要傳遞子組件,子組件所須要的props在父組件會封裝好,引用子組件的時候就不必傳遞全部props了。

組合組件核心的兩個方法是React.Children.mapReact.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.mapReact.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) 等等。明白這些代碼設計思想,未來咱們也能很快地掌握新的組件設計模式。

參考連接:

相關文章
相關標籤/搜索