前情提要:昨天我們把原本的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調用不同的方法,它們有著不同的輸出內容。但他們的執行是相似的
這些邏輯是共用的,差別只在於資料的不同。
我們需要一個抽象概念,讓我們在一個地方定義邏輯,並可以在多個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差異的部分
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,也不會使用繼承來複製行為。
這邊有兩項拆解的概念
大概知道HOC的原理與做法後,本來想再拿來實行在todos的...
但todos太簡單了想不到 ಥ_ಥ
明天再繼續吧
之前看到React HOC,一直在想Vue怎麼套這個觀念進來,然後無限loop這個低能問題好久,直到Hunter敲醒我Vue就有mixin、slot scoped去處理共用邏輯的部分XD