iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 15
2
Modern Web

React 30天系列 第 15

Day 15-[番外]關於HOC

前情提要:昨天我們把原本的todos和redux結合,實作了state、store、reducer和action等redux要件,如何透過react-redux的Provider與connect結合使用。今天稍微跳脫一下看看HOC吧!

HOC也就是Higher-Order Components,它是一個進階技巧讓component的邏輯重複使用,事實上只是一個包了另一個component的React component。
接著先從React官網範例了解HOC的概念吧!

const EnhancedComponent = higherOrderComponent(WrappedComponent);

component將props轉為UI,而HOC將component轉為另一個component。
HOC在第三方的React library中很常見,例如Redux的connect和Relay的createFragmentContainer
這也是為什麼會想提到HOC的原因

React的靈魂角色component,也是程式碼重用的主要單位,但當我們用了一段時間後會發現一些模式並不適合傳統的component。

比如說有一個CommentList component,透過取得global資料render評論清單

class CommentList extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {
      // "DataSource" is some global data source
      comments: DataSource.getComments()
    };
  }

  componentDidMount() {
    // Subscribe to changes
    DataSource.addChangeListener(this.handleChange);
  }

  componentWillUnmount() {
    // Clean up listener
    DataSource.removeChangeListener(this.handleChange);
  }

  handleChange() {
    // Update component state whenever the data source changes
    this.setState({
      comments: DataSource.getComments()
    });
  }

  render() {
    return (
      <div>
        {this.state.comments.map((comment) => (
          <Comment comment={comment} key={comment.id} />
        ))}
      </div>
    );
  }
}

接著可能有另一個component訂閱部落格貼文,資料也是由global取得,接著發現他跟上面的commentList有相似的處理行為

class BlogPost extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {
      blogPost: DataSource.getBlogPost(props.id)
    };
  }

  componentDidMount() {
    DataSource.addChangeListener(this.handleChange);
  }

  componentWillUnmount() {
    DataSource.removeChangeListener(this.handleChange);
  }

  handleChange() {
    this.setState({
      blogPost: DataSource.getBlogPost(this.props.id)
    });
  }

  render() {
    return <TextBlock text={this.state.blogPost} />;
  }
}

CommentList和BlogPost不同,它們在DataSource調用不同的方法,它們有著不同的輸出內容。但他們的執行是相似的

  1. 當component掛載後,向DataSource增加change listener
  2. 透過handleChange,當data sourece更變時調用setState
  3. component要卸載時移除change listener

這些邏輯是共用的,差別只在於資料的不同。
我們需要一個抽象概念,讓我們在一個地方定義邏輯,並可以在多個component間共用,這也就是HOC出色的地方。

預期這個HOC需要兩個參數,其中一個為子component,另一個為取得資料的function

const CommentListWithSubscription = withSubscription(
  CommentList,
  (DataSource) => DataSource.getComments()
);

const BlogPostWithSubscription = withSubscription(
  BlogPost,
  (DataSource, props) => DataSource.getBlogPost(props.id)
);

接著我們新建一個component來定義邏輯,這裡我只保留和CommentList及BlogPost差異的部分

  • 可以看到傳入的參數有:
    WrappedComponent: 要被包的component。
    selectData: 取得data的function
  • 輸出的component除了原本丟進來的WrappedComponent和props,還另外新增了透過selectData取得的data
function withSubscription(WrappedComponent, selectData) {
  return class extends React.Component {
    constructor(props) {
      super(props);
      // ...
      this.state = {
        data: selectData(DataSource, props)
      };
    }
    // ...
    handleChange() {
      this.setState({
        data: selectData(DataSource, this.props)
      });
    }

    render() {
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

注意: HOC不會修改輸入的component,也不會使用繼承來複製行為。
這邊有兩項拆解的概念

  1. HOC不關心data的使用方式或原因
  2. 被包裝的component也不會關心data從哪來

大概知道HOC的原理與做法後,本來想再拿來實行在todos的...

但todos太簡單了想不到 ಥ_ಥ
明天再繼續吧

參考資料:Higher-Order Components


上一篇
Day 14-和Redux合作重寫todos吧!
下一篇
Day 16-[番外]HOC簡易實例
系列文
React 30天30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Ben the dust
iT邦新手 5 級 ‧ 2018-10-25 11:28:57

之前看到React HOC,一直在想Vue怎麼套這個觀念進來,然後無限loop這個低能問題好久,直到Hunter敲醒我Vue就有mixin、slot scoped去處理共用邏輯的部分XD

我要留言

立即登入留言