HOC(全稱Higher-order component)是一種React的進階使用方法,主要仍是爲了便於組件的複用。HOC就是一個方法,獲取一個組件,返回一個更高級的組件。html
在React開發過程當中,發現有不少狀況下,組件須要被"加強",好比說給組件添加或者修改一些特定的props,一些權限的管理,或者一些其餘的優化之類的。而若是這個功能是針對多個組件的,同時每個組件都寫一套相同的代碼,明顯顯得不是很明智,因此就能夠考慮使用HOC。react
栗子:react-redux的connect方法就是一個HOC,他獲取wrappedComponent,在connect中給wrappedComponent添加須要的props。git
HOC不只僅是一個方法,確切說應該是一個組件工廠,獲取低階組件,生成高階組件。github
一個最簡單的HOC實現是這個樣子的:redux
function HOCFactory(WrappedComponent) {
return class HOC extends React.Component {
render(){
return <WrappedComponent {...this.props} />
}
}
}
複製代碼
其實,除了代碼複用和模塊化,HOC作的其實就是劫持,因爲傳入的wrappedComponent是做爲一個child進行渲染的,上級傳入的props都是直接傳給HOC的,因此HOC組件擁有很大的權限去修改props和控制渲染。bash
能夠經過對傳入的props進行修改,或者添加新的props來達到增刪改props的效果。app
好比你想要給wrappedComponent增長一個props,能夠這麼搞:模塊化
function control(wrappedComponent) {
return class Control extends React.Component {
render(){
let props = {
...this.props,
message: "You are under control"
};
return <wrappedComponent {...props} />
}
}
}
複製代碼
這樣,你就能夠在你的組件中使用message這個props:優化
class MyComponent extends React.Component {
render(){
return <div>{this.props.message}</div>
}
}
export default control(MyComponent);
複製代碼
這裏的渲染劫持並非你能控制它渲染的細節,而是控制是否去渲染。因爲細節屬於組件內部的render方法控制,因此你沒法控制渲染細節。ui
好比,組件要在data沒有加載完的時候,現實loading...,就能夠這麼寫:
function loading(wrappedComponent) {
return class Loading extends React.Component {
render(){
if(!this.props.data) {
return <div>loading...</div>
}
return <wrappedComponent {...props} />
}
}
}
複製代碼
這個樣子,在父級沒有傳入data的時候,這一起就只會顯示loading...,不會顯示組件的具體內容
class MyComponent extends React.Component {
render(){
return <div>{this.props.data}</div>
}
}
export default control(MyComponent);
複製代碼
最經典的就是React Redux的connect方法(具體在connectAdvanced中實現)。
經過這個HOC方法,監聽redux store,而後把下級組件須要的state(經過mapStateToProps獲取)和action creator(經過mapDispatchToProps獲取)綁定到wrappedComponent的props上。
這個是官網上的一個示例,能夠用來監控父級組件傳入的props的改變:
function logProps(WrappedComponent) {
return class extends React.Component {
componentWillReceiveProps(nextProps) {
console.log(`WrappedComponent: ${WrappedComponent.displayName}, Current props: `, this.props);
console.log(`WrappedComponent: ${WrappedComponent.displayName}, Next props: `, nextProps);
}
render() {
// Wraps the input component in a container, without mutating it. Good!
return <WrappedComponent {...this.props} />;
}
}
}
複製代碼
能夠經過HOC對組件進行包裹,當跳轉到當前頁面的時候,檢查用戶是否含有對應的權限。若是有的話,渲染頁面。若是沒有的話,跳轉到其餘頁面(好比無權限頁面,或者登錄頁面)。
也能夠給當前組件提供權限的API,頁面內部也能夠進行權限的邏輯判斷。
儘可能不要隨意修改下級組件須要的props 之因此這麼說,是由於修改父級傳給下級的props是有必定風險的,可能會形成下級組件發生錯誤。好比,本來須要一個name的props,可是在HOC中給刪掉了,那麼下級組件或許就沒法正常渲染,甚至報錯。
之前你在父組件中使用<component ref="component"/>
的時候,你能夠直接經過this.refs.component
進行獲取。可是由於這裏的component通過HOC的封裝,已是HOC裏面的那個component了,因此你沒法獲取你想要的那個ref(wrappedComponent的ref)
。
要解決這個問題,這裏有兩個方法:
a) 像React Redux的connect方法同樣,在裏面添加一個參數,好比withRef
,組件中檢查到這個flag了,就給下級組件添加一個ref,並經過getWrappedInstance方法獲取。
栗子:
function HOCFactory(wrappedComponent) {
return class HOC extends React.Component {
getWrappedInstance = ()=>{
if(this.props.widthRef) {
return this.wrappedInstance;
}
}
setWrappedInstance = (ref)=>{
this.wrappedInstance = ref;
}
render(){
let props = {
...this.props
};
if(this.props.withRef) {
props.ref = this.setWrappedInstance;
}
return <wrappedComponent {...props} />
}
}
}
export default HOCFactory(MyComponent);
複製代碼
這樣子你就能夠在父組件中這樣獲取MyComponent的ref值了。
class ParentCompoent extends React.Component {
doSomethingWithMyComponent(){
let instance = this.refs.child.getWrappedInstance();
// ....
}
render(){
return <MyComponent ref="child" withRef />
}
}
複製代碼
b) 還有一種方法,在官網中有提到過: 父級經過傳遞一個方法,來獲取ref,具體看栗子:
先看父級組件:
class ParentCompoent extends React.Component {
getInstance = (ref)=>{
this.wrappedInstance = ref;
}
render(){
return <MyComponent getInstance={this.getInstance} />
}
}
複製代碼
HOC裏面把getInstance方法看成ref的方法傳入就好
function HOCFactory(wrappedComponent) {
return class HOC extends React.Component {
render(){
let props = {
...this.props
};
if(typeof this.props.getInstance === "function") {
props.ref = this.props.getInstance;
}
return <wrappedComponent {...props} />
}
}
}
export default HOCFactory(MyComponent);
複製代碼
好比,你原來在Component上面綁定了一些static方法MyComponent.staticMethod = o=>o
。可是因爲通過HOC的包裹,父級組件拿到的已經不是原來的組件了,因此固然沒法獲取到staticMethod方法了。
官網上的示例:
// 定義一個static方法
WrappedComponent.staticMethod = function() {/*...*/}
// 利用HOC包裹
const EnhancedComponent = enhance(WrappedComponent);
// 返回的方法沒法獲取到staticMethod
typeof EnhancedComponent.staticMethod === 'undefined' // true
複製代碼
這裏有一個解決方法,就是hoist-non-react-statics組件,這個組件會自動把全部綁定在對象上的非React方法都綁定到新的對象上:
import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
hoistNonReactStatic(Enhance, WrappedComponent);
return Enhance;
}
複製代碼
當你須要作React插件的時候,HOC模型是一個很實用的模型。
但願這篇文章能幫你對HOC有一個大概的瞭解和啓發。
另外,這篇medium上的文章會給你更多的啓發,在這篇文章中,我這裏講的被分爲Props Proxy HOC,還有另一種Inheritance Inversion HOC,強烈推薦看一看。