[react] Higher Order Component(HOC)
Higher Order Component 指的是在 React 中能夠幫助我們重複使用程式碼的 React Component。具體來說 Higher Order Component 是一個 function,而這個 function 可以把 Component 當作參數傳入,並且回傳一個「增強版」的 Component。
- 被當作參數放入的 Component 稱作 Wrapped Component (ChildComponent),因為它是被 HOC 包住的。
- Higher-Order Component 又稱作 Enhanced Component 或 Composed Component,但它其實是 Function。
最常見的就是 react-redux
中的 connect()
這個函式。
撰寫 Higher Order Component
撰寫 Higher Order Component(HOC)的流程如下:
- 將你想要重複使用的程式碼或邏輯撰寫成 React Component
- 建立 HOC 檔案,並撰寫草稿
- 將想要重複使用的程式碼或邏輯搬移到 HOC 檔案中
- 將 props/config/behavior 傳遞到子元件(child component)中
HOC boilerplate
// '@/components/HocComponent'
import React from 'react';
const higherOrderComponent = (ChildComponent) => {
class ComposedComponent extends React.Component {
render() {
// 記得要用 ...this.props 把所有原本的 props 內容帶回到 ChildComponent 中
return <ChildComponent {...this.props} />;
}
}
return ComposedComponent;
};
export default higherOrderComponent;
❗ 記得要在
return <ChildComponent>
中使用{...this.props}
把原有的 props 代到 ChildComponent 中。
使用 HOC 的方式是在要套用此 HOC 的元件載入,並在最後 export
的時候呼叫它:
// '@/components/ChildComponent'
import HocComponent from '@/component/HocComponent';
class WrappedComponent {
// ...
render() { ... }
}
export default HocComponent(WrappedComponent);
範例程式碼
/* This is a HOC */
import React from 'react';
import { connect } from 'react-redux';
export default (ChildComponent) => {
class ComposedComponent extends React.Component {
// Our component just got rendered
componentDidMount() {
this.verifyAuth();
}
// Our component just got updated
componentDidUpdate() {
this.verifyAuth();
}
verifyAuth() {
if (this.props.auth) {
return;
}
this.props.history.push('/');
}
render() {
// 記得要用 ...this.props 把所有原本的 props 內容帶回到 ChildComponent 中
return <ChildComponent {...this.props} />;
}
}
const mapStateToProps = (state) => ({
auth: state.auth,
});
return connect(mapStateToProps)(ComposedComponent);
};
慣例與注意事項
慣例:透過 Wrapped Component 將無關的 Props 傳入
HOCs 只是為元件添加功能,所以要把被當作參數傳入的 Component 提供相同的 props。因此要把這個 HOC 用不到的 props 全部在還回到原本的 Component 中:
render() {
// 將在這個 HOC 需要用到的 props 取出,其於的 props 傳回原本的 Wrapped Component 內
const { extraProp, ...passThroughProps } = this.props;
// 灌入新的 props 進去原本的 Wrapped Component 中
const injectedProp = someStateOrInstanceMethod;
return (
// 把這些 Props 都放回當作參數傳入的 Wrapped Component
<WrappedComponent
injectedProp={injectedProp}
{...passThroughProps}
/>
);
}
慣例:最大化相容性(composability)
撰寫 HOC 時有幾種不同做法,但建議上還是讓 HOC 只帶一個參數(即,WrappedComponent
):
// enhancedComponent(WrappedComponent)
const NavbarWithRouter = withRouter(Navbar);
// enhancedComponent(config)(WrappedComponent)
const ConnectedComment = connect(commentSelector, commentActions)(CommentList);
// NOT RECOMMENDED: enhancedComponent(WrappedComponent, config)
const CommentWithRelay = Relay.createContainer(Comment, config);
因為在許多第三方提供的套件中,都有提供 compose
utility function,它可以把多個 HOC 連接起 來,但前提是這些 HOC 都只能接受 WrappedComponent
當作它的單一參數:
const enhance = compose(
// 這些都是只接受單一參數的 HOC
withRouter,
connect(commentSelector),
);