React高階組件總結(轉)

在多個不同的組件中需要用到相同的功能,這個解決方法,通常有Mixin和高階組件。
Mixin方法例如:

//給所有組件添加一個name屬性
var defaultMixin = {
    getDefaultProps: function() {
        return {
            name: "Allen"
        }
    }
}

var Component = React.createClass({
    mixins: [defaultMixin],
    render: function() {
        return <h1>Hello, {this.props.name}</h1>
    }
})

但是由于Mixin過多會使得組件難以維護,在React ES6中Mixin不再被支持。
高階組件其實是一個函數,接收一個組件作為參數,返回一個包裝組件作為返回值,類似于高階函數。高階組件和裝飾器就是一個模式,因此,高階組件可以作為裝飾器來使用。
高階組件有如下好處:

  1. 適用范圍廣,它不需要es6或者其它需要編譯的特性,有函數的地方,就有HOC。
  2. Debug友好,它能夠被React組件樹顯示,所以可以很清楚地知道有多少層,每層做了什么。
//高階組件基本形式:
const EnhancedComponent = higherOrderComponent(WrappedComponent);

詳細如下:

function hoc(ComponentClass) {
    return class HOC extends React.Component {
        componentDidMount() {
            console.log("hoc");
        }

        render() {
            return <ComponentClass />
        }
    }
}
//使用高階組件
class ComponentClass extends React.Component {
    render() {
        return <div></div>
    }
}

export default hoc(MyComponent);

//作為裝飾器使用
@hoc
export default class ComponentClass extends React.Component {
    //...
}

高階組件有兩種常見的用法:

  1. 屬性代理(Props Proxy): 高階組件通過ComponentClass的props來進行相關操作
  2. 繼承反轉(Inheritance Inversion)): 高階組件繼承自ComponentClass

1. 屬性代理(Props Proxy)

屬性代理有如下4點常見作用:

  1. 操作props
  2. 通過refs訪問組件實例
  3. 提取state
  4. 用其他元素包裹WrappedComponent,實現布局等目的
(1). 操作props

可以對原組件的props進行增刪改查,通常是查找和增加,刪除和修改的話,需要考慮到不能破壞原組件。
下面是添加新的props:

function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      const newProps = {
        user: currentLoggedInUser
      }
      return <WrappedComponent {...this.props} {...newProps}/>
    }
  }
}
(2). 通過refs訪問組件實例

可以通過ref回調函數的形式來訪問傳入組件的實例,進而調用組件相關方法或其他操作。
例如:

//WrappedComponent初始渲染時候會調用ref回調,傳入組件實例,在proc方法中,就可以調用組件方法
function refsHOC(WrappedComponent) {
  return class RefsHOC extends React.Component {
    proc(wrappedComponentInstance) {
      wrappedComponentInstance.method()
    }

    render() {
      const props = Object.assign({}, this.props, {ref: this.proc.bind(this)})
      return <WrappedComponent {...props}/>
    }
  }
}
(3). 提取state

你可以通過傳入 props 和回調函數把 state 提取出來,類似于 smart component 與 dumb component。
提取 state 的例子:提取了 input 的 value 和 onChange 方法。這個簡單的例子不是很常規,但足夠說明問題。

function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    constructor(props) {
      super(props)
      this.state = {
        name: ''
      }

      this.onNameChange = this.onNameChange.bind(this)
    }
    onNameChange(event) {
      this.setState({
        name: event.target.value
      })
    }
    render() {
      const newProps = {
        name: {
          value: this.state.name,
          onChange: this.onNameChange
        }
      }
       return <WrappedComponent {...this.props} {...newProps}/>
    }
  }
}

//使用方式如下
@ppHOC
class Example extends React.Component {
  render() {
    //使用ppHOC裝飾器之后,組件的props被添加了name屬性,可以通過下面的方法,將value和onChange添加到input上面
    //input會成為受控組件
    return <input name="name" {...this.props.name}/>
  }
}
(4). 包裹WrappedComponent

為了封裝樣式、布局等目的,可以將WrappedComponent用組件或元素包裹起來。
例如:

function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      return (
        <div style={{display: 'block'}}>
          <WrappedComponent {...this.props}/>
        </div>
      )
    }
  }
}

2. 繼承反轉(Inheritance Inversion)

HOC繼承了WrappedComponent,意味著可以訪問到WrappedComponent的state,props,生命周期和render方法。如果在HOC中定義了與WrappedComponent同名方法,將會發生覆蓋,就必須手動通過super進行調用。通過完全操作WrappedComponent的render方法返回的元素樹,可以真正實現渲染劫持。這種思想具有較強的入侵性。
大致形式如下:

function ppHOC(WrappedComponent) {
  return class ExampleEnhance extends WrappedComponent {
    ...
    componentDidMount() {
      super.componentDidMount();
    }
    componentWillUnmount() {
      super.componentWillUnmount();
    }
    render() {
      ...
      return super.render();
    }
  }
}

例如,實現一個顯示loading的請求。組件中存在網絡請求,完成請求前顯示loading,完成后再顯示具體內容。
可以用高階組件實現如下:

function hoc(ComponentClass) {
    return class HOC extends ComponentClass {
        render() {
            if (this.state.success) {
                return super.render()
            }
            return <div>Loading...</div>
        }
    }
}

@hoc
export default class ComponentClass extends React.Component {
    state = {
        success: false,
        data: null
    };
    async componentDidMount() {
        const result = await fetch(...請求);          
     this.setState({
            success: true,
            data: result.data
        });
    }
    render() {
        return <div>主要內容</div>
    }
}
(1) 渲染劫持

繼承反轉這種模式,可以劫持被繼承class的render內容,進行修改,過濾后,返回新的顯示內容。
之所以被稱為渲染劫持是因為 HOC 控制著 WrappedComponent 的渲染輸出,可以用它做各種各樣的事。

通過渲染劫持,你可以完成:

在由 render輸出的任何 React 元素中讀取、添加、編輯、刪除 props
讀取和修改由 render 輸出的 React 元素樹
有條件地渲染元素樹
把樣式包裹進元素樹,就行Props Proxy那樣包裹其他的元素

注:在 Props Proxy 中不能做到渲染劫持。
雖然通過 WrappedComponent.prototype.render 你可以訪問到 render 方法,不過還需要模擬 WrappedComponent 的實例和它的 props,還可能親自處理組件的生命周期,而不是交給 React。記住,React 在內部處理了組件實例,你處理實例的唯一方法是通過 this 或者 refs。

export const hocInversion2 = config => (ComponentClass) => {
    return class hoc extends ComponentClass {
       render() {
        const { style = {} } = config;
        const elementsTree = super.render();
        if (config.type === 'add-style') {
            return <div style={{...style}}>
              {elementsTree}
            </div>;
        }
        return elementsTree;
       }
    }
}

@hocInversion2({type: 'add-style', style: { color: 'red'}})
(2) 操作state

HOC可以讀取,編輯和刪除WrappedComponent實例的state,可以添加state。不過這個可能會破壞WrappedComponent的state,所以,要限制HOC讀取或添加state,添加的state應該放在單獨的命名空間里,而不是和WrappedComponent的state混在一起。
例如:通過訪問WrappedComponent的props和state來做調試

export const IIHOCDEBUGGER = (WrappedComponent) => {
    return class II extends WrappedComponent {
      render() {
        return (
          <div>
            <h2>HOC Debugger Component</h2>
            <p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre>
            <p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre>
            {super.render()}
          </div>
        )
      }
    }
  }
(3) 條件渲染

當 this.props.loggedIn 為 true 時,這個 HOC 會完全渲染 WrappedComponent 的渲染結果。(假設 HOC 接收到了 loggedIn 這個 prop)

function iiHOC(WrappedComponent) {
  return class Enhancer extends WrappedComponent {
    render() {
      if (this.props.loggedIn) {
        return super.render()
      } else {
        return null
      }
    }
  }
}
(4) 解決WrappedComponent名字丟失問題

用HOC包裹的組件會丟失原先的名字,影響開發和調試。可以通過在WrappedComponent的名字上加一些前綴來作為HOC的名字,以方便調試。
例如:

//或
class HOC extends ... {
  static displayName = `HOC(${getDisplayName(WrappedComponent)})`
  ...
}

//getDisplayName
function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName ||
         WrappedComponent.name ||
         ‘Component’
}
(5) 實際應用
1. mobx-react就是高階組件是一個實際應用

@observer裝飾器將組件包裝為高階組件,傳入組件MyComponent后,mobx-react會對其生命周期進行各種處理,并通過調用forceUpdate來進行刷新實現最小粒度的渲染。mobx提倡一份數據引用,而redux中則提倡immutable思想,每次返回新對象。

2. 實現一個從localStorage返回記錄的功能
//通過多重高階組件確定key并設定組件
const withStorage = (key) => (WrappedComponent) => {
  return class extends Component {
    componentWillMount() {
        let data = localStorage.getItem(key);
        this.setState({data});
    }
    render() {
      return <WrappedComponent data={this.state.data} {...this.props} />
    }
  }
}

@withStorage('data')
class MyComponent2 extends Component {  
    render() {
        return <div>{this.props.data}</div>
    }
}

@withStorage('name')
class MyComponent3 extends Component {  
    render() {
        return <div>{this.props.data}</div>
    }
}
3. 實現打點計時功能
(1). Props Proxy方式
//性能追蹤:渲染時間打點
export default (Target) => (props)=>{
    let func1 = Target.prototype['componentWillMount']    
    let func2 = Target.prototype['componentDidMount']//Demo并沒有在prototype上定義該方法,func2為undefined,但是并不會有影響,這樣做只是為了事先提取出可能定義的邏輯,保持原函數的純凈
    let begin, end;
    Target.prototype['componentWillMount'] = function (...argus){//do not use arrow funciton to bind 'this' object
        func1.apply(this,argus);//執行原有的邏輯
        begin = Date.now();
    }
    Target.prototype['componentDidMount'] = function (...argus){
        func2.apply(this,argus);//執行原有的邏輯
        end = Date.now();
        console.log(Target.name+'組件渲染時間:'+(end-begin)+'毫秒')
    }
    return <Target {...props}/>//do not forget to pass props to the element of Target
}
(2) Inheritance Inversion方式
// 另一種HOC的實現方式 Inheritance Inversion
export default Target => class Enhancer extends Target {
    constructor(p){
        super(p);//es6 繼承父類的this對象,并對其修改,所以this上的屬性也被繼承過來,可以訪問,如state
        this.end =0;
        this.begin=0;
    }
    componentWillMount(){
        super.componentWilMount && super.componentWilMount();// 如果父類沒有定義該方法,直接調用會出錯
        this.begin = Date.now();
    }
    componentDidMount(){
        super.componentDidMount && super.componentDidMount();
        this.end=Date.now();
        console.log(Target.name+'組件渲染時間'+(this.end-this.begin)+'ms')
    }
    render(){
        let ele = super.render();//調用父類的render方法
        return ele;//可以在這之前完成渲染劫持
    }
}

原文地址:
https://www.cnblogs.com/mengff/p/9657232.html

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容