React高階組件以及使用場景

本文章重點講述React高階組件的使用場景紧索,附帶講述高階函數(shù)紧帕,因為兩者很類似睬隶。對比起來更容易理解

什么是高階函數(shù):一個函數(shù)的參數(shù)是一個函數(shù),或者 函數(shù)的返回值是一個函數(shù)减牺,我們稱這類函數(shù)是高階函數(shù)豌习。

例如:

const Mozi = function(name1){
   return function (name2){
        return name1 + ' ' + name2;
    };
};

上面函數(shù)的執(zhí)行過程如下

> Mozi('墨子')  
<- function(name2){
        return name1 + ' ' + name2;
   }
> Mozi('墨子')('工程')  
<- '墨子 工程'

執(zhí)行函數(shù)Mozi('墨子') 返回的是一個函數(shù),我們執(zhí)行 Mozi('墨子')('工程') 相當(dāng)于執(zhí)行了返回的函數(shù)

高階函數(shù)實際的作用是什么拔疚?

以下面例子肥隆,我們要把數(shù)組的每一項變成mozi_n的方式

我們一般會用下面的方法進行實現(xiàn)

function format(item) {
    return  `mozi_${item}`;
}

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var result = [];
for (var i=0; i<arr.length; i++) {
    result.push(format(arr[i]));
}
console.log(result) // ["mozi_1", "mozi_2", "mozi_3", "mozi_4", "mozi_5", "mozi_6", "mozi_7", "mozi_8", "mozi_9"]

如果用高階函數(shù)map來實現(xiàn),如下

function format(item) {
    return  `mozi_${item}`;
}
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(format);  // ["mozi_1", "mozi_2", "mozi_3", "mozi_4", "mozi_5", "mozi_6", "mozi_7", "mozi_8", "mozi_9"]

上例中map把對數(shù)組的遍歷進行了抽象封裝稚失,我們在對數(shù)組進行操作的時候不用關(guān)心map內(nèi)部是怎么實現(xiàn)的栋艳,只需要將我們對數(shù)組的每一項的處理函數(shù)傳進去,就會返回處理后的新數(shù)組句各。

這樣做的好處是簡潔吸占,通用晴叨,我們再對數(shù)組進行其他操作的時候就不需要重復(fù)寫for循環(huán)來處理,例如

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// 把數(shù)組每一項轉(zhuǎn)換成字符串矾屯,只需要一行代碼
arr.map(String);  // ["1", "2", "3", "4", "5", "6", "7", "8", "9"]

上面兩個例子來簡單描述了一下什么是高階函數(shù)兼蕊,以及簡單用法。(有空再看一下MOZI中對redux connect的封裝)

什么是React高階組件:一個組件的參數(shù)是組件件蚕,并且返回值是一個組件孙技,我們稱這類組件為高階組件

React 中的高階組件主要有兩種形式:屬性代理反向繼承
屬性代理: 是 一個函數(shù)接受一個 WrappedComponent 組件作為參數(shù)傳入排作,并返回一個繼承了 React.Component 組件的類牵啦,且在該類的 render() 方法中返回被傳入的 WrappedComponent 組件
屬性代理形如:

function MoziComponent(WrappedComponent) {
    return class extends React.Component {
        render() {
            return <WrappedComponent {...this.props} />;
        }
    };
}

屬性代理類高階組件可以做什么?

  • 操作 props
  • 用其他元素包裹傳入的組件 WrappedComponent
  • 通過 ref 訪問到組件實例
  • 抽離 state

一:操作 props(增加新props)

function MoziComponent(WrappedComponent) {
   return class extends WrappedComponent {
        render() {
            const newProps = {
                mozi: 'mozi'
            }
      return <WrappedComponent {...this.props,..newProps} />
        }
    };
}

二:用其他元素包裹傳入的組件 WrappedComponent

function MoziComponent(WrappedComponent) {
   return class extends WrappedComponent {
        render() {
            const newProps = {
                mozi: 'mozi'
            }
      return  <div style={{display: 'block'}}>
                    <WrappedComponent/>
               </div>
        }
    };
}

三:通過 ref 訪問到組件實例(在高階組件中獲取到組件實例可以直接操作dom妄痪,比如input蕾久,但是實用場景很少,有些很難用state去實現(xiàn)的復(fù)雜動畫之類的效果拌夏,用ref直接操作dom更方便一些)

import React, { Component } from 'react';

const header_hoc = WrappedComponent => {
  return class  header_hocmozi extends Component {
   
      renderHeader = () => {
          return <div style={{margin:'0 auto',textAlign:'center'}}>
              全局頭部
          </div>
      }
      getRef(ComponentRef) { // 看這里
        if(ComponentRef && ComponentRef.home) {
          console.log('wrappedComponentInstance-----',ComponentRef.home)
        }
       
      }
      render() {
        const __props ={...this.props,ref:this.getRef.bind(this) }
        return <div>
               { this.renderHeader() }
               <WrappedComponent {...__props}/>
            </div>
      }
  }

}
export default header_hoc;

四:抽離 state(因為屬性代理類的高階組件可以造作一切react有的方法或者屬性,但是不建議在高階函數(shù)里操作state履因,因為state屬于一個組件的私有行為障簿,一旦在高階組件里做了一些state的更改刪除之類的,容易把原有組件覆蓋或者誤操作栅迄,出了問題也很難去定位站故。)

反向繼承:是 一個函數(shù)接受一個 WrappedComponent 組件作為參數(shù)傳入,并返回一個繼承了該傳入 WrappedComponent 組件的類毅舆,且在該類的 render() 方法中返回 super.render() 方法西篓。
是因為傳入組件被動地被返回的Mozi繼承,而不是 WrappedComponent 去繼承 Mozi憋活。通過這種方式他們之間的關(guān)系倒轉(zhuǎn)了岂津,所以叫反向繼承。
反向繼承形如:

function MoziComponent(WrappedComponent) {
   return class Mozi extends WrappedComponent {
        render() {
            return super.render();
        }
    };
}

反向繼承因為繼承了傳入的組件悦即,所以你可以獲取傳入組件(WrappedComponent)的state吮成,props,render辜梳,以及整個生命周期

可以用反向繼承高階組件做什么粱甫?
  • 渲染劫持
  • State 抽象和更改
1 - 條件渲染【渲染劫持】

如下,通過 props.isLoading 這個條件來判斷渲染哪個組件

const withLoading = (WrappedComponent) => {
    return class extends WrappedComponent {
        render() {
            if(this.props.isLoading) {
                return <Loading />;
            } else {
                return super.render();
            }
        }
    };
}
2 - 修改要渲染的元素【渲染劫持】

以下demo只是展示了如何通過修改element作瞄,意味著你可以在高階組件中隨意控制返回你要渲染的元素茶宵。
比如:我們的權(quán)限控制有頁面級別的,也有細粒度的按鈕級別的宗挥,渲染劫持類高階組件就最適合細粒度的把控每個小組件的渲染權(quán)限乌庶,根據(jù)接口返回是否要展示某一個按鈕 or 某一個組件种蝶。(并且不侵入具體業(yè)務(wù)代碼,如果沒有用高階組件去抽離這部分細粒度的權(quán)限控制你就需要在業(yè)務(wù)代碼中去寫安拟,會變得很難維護)

/*
組件修改蛤吓,組件級別權(quán)限控制
*/
const edit_hoc = WrappedComponent => {
  return class extends WrappedComponent {
    render() {
      const tree = super.render();
     
      let props = {
          ...tree.props,
      };

      props.children.map(item=>{
        item && item.type == 'input' && (item.props.value = '墨子工程')
      })
      
      const newTree = React.cloneElement(tree, props, tree.props.children);
      return newTree;
    }
  };
};  

export default edit_hoc;

實用場景

1- 組件渲染性能追蹤
//performance_hoc.js
const hoc = WrappedComponent => {
  return class performance_hoc extends WrappedComponent {
    constructor(props) {
        super(props);
        this.start = 0;
        this.end = 0;
    }
   componentWillMount() {
        super.componentWillMount && super.componentWillMount();
        this.start = Date.now();
    }
    componentDidMount() {
      super.componentDidMount && super.componentDidMount();
      this.end = Date.now();
      console.log(`%c ${WrappedComponent.name} 組件渲染時間為 ${this.end - this.start} ms`,'font-size:20px;color:red;');
    }
    render() {
      return super.render()
    }
  };
};  

export default hoc;

使用方式

import React, { Component } from 'react';
import hoc from './performance_hoc';
@hoc
export default class Performance extends Component {
  render() {
    return <div>墨子工程</div>
  }
}

2 - 權(quán)限控制
import React, { Component } from 'react';

const auth_hoc = role => WrappedComponent => {
  return class extends React.Component {
      state = {
          permission: false,
          loading: true
      }

      showLoading = (isShow) => {
        this.setState({
            loading: isShow,
        });
      }

      checkAuth = () => { // 模擬返回角色的api
       return new Promise((reslove,reject)=>{
          this.showLoading(true)
          setTimeout(_=>{
            reslove('admin')
            this.showLoading(false)
          },2000)
        })
      }

      async componentWillMount() {
          const __role = await this.checkAuth();
          this.setState({
              permission: __role == role,
          });
      }

      render() {
        const { loading, permission } = this.state

          if(loading) {
            return <div>loading...</div>
          }

          if (permission) {
              return <WrappedComponent {...this.props} />;
          } else {
              return (<div>您沒有權(quán)限查看該頁面!</div>);
          }
      }
  };
}
export default auth_hoc;

使用(比如:產(chǎn)品需求是這個頁面只有admin方式的才能展示 or 只有vip方式的才能展示糠赦,就可以通過高階組件去抽離会傲,你也不需要每一個權(quán)限都寫一個高階組件,只需要 傳遞不同的權(quán)限去接口驗證就ok)

import React, { Component } from 'react';
import auth_hoc from './auth_hoc'; 
@auth_hoc('admin') //vip
export default class AuthPage extends Component {
  constructor(...args) {
    super(...args);
  }
  render() {
    return (
      <div className="Home">
         墨子工程
      </div>
    );
  }
}

3 - 組件&邏輯復(fù)用
//header_hoc.js
import React, { Component } from 'react';

const header_hoc = WrappedComponent => {
  return class  header_hocmozi extends Component {
   
      renderHeader = () => {
          return <div style={{margin:'0 auto',textAlign:'center'}}>
              全局頭部
          </div>
      }
      render() {
        return <div>
               { this.renderHeader() }
               <WrappedComponent {...this.props}/>
            </div>
      }
  }

}
export default header_hoc;

使用

import React, { Component } from 'react';
import header_hoc from './header_hoc';

@header_hoc
export default class Home extends Component {
  render() {
    return (
      <div className="Home">
        首頁
      </div>
    );
  }
}

高階組件其實就是裝飾器模式在 React 中的實現(xiàn):通過給函數(shù)傳入一個組件拙泽,在函數(shù)內(nèi)部對該組件進行功能的增強淌山,最后返回這個組件

總結(jié):

  • 高階組件 不是組件,是 一個把某個組件轉(zhuǎn)換成另一個組件的 函數(shù)
  • 高階組件的主要作用是 代碼復(fù)用
  • 高階組件是 裝飾器 在 React 中的實現(xiàn)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末顾瞻,一起剝皮案震驚了整個濱河市泼疑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荷荤,老刑警劉巖退渗,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蕴纳,居然都是意外死亡会油,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門古毛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來翻翩,“玉大人,你說我怎么就攤上這事稻薇∩┒常” “怎么了?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵塞椎,是天一觀的道長桨仿。 經(jīng)常有香客問我,道長案狠,這世上最難降的妖魔是什么蹬敲? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮莺戒,結(jié)果婚禮上伴嗡,老公的妹妹穿的比我還像新娘。我一直安慰自己从铲,他們只是感情好瘪校,可當(dāng)我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般阱扬。 火紅的嫁衣襯著肌膚如雪泣懊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天麻惶,我揣著相機與錄音馍刮,去河邊找鬼。 笑死窃蹋,一個胖子當(dāng)著我的面吹牛卡啰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播警没,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼匈辱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了杀迹?” 一聲冷哼從身側(cè)響起亡脸,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎树酪,沒想到半個月后浅碾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡续语,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年及穗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绵载。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖苛白,靈堂內(nèi)的尸體忽然破棺而出娃豹,到底是詐尸還是另有隱情,我是刑警寧澤购裙,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布懂版,位于F島的核電站,受9級特大地震影響躏率,放射性物質(zhì)發(fā)生泄漏躯畴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一薇芝、第九天 我趴在偏房一處隱蔽的房頂上張望蓬抄。 院中可真熱鬧,春花似錦夯到、人聲如沸嚷缭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阅爽。三九已至路幸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間付翁,已是汗流浹背简肴。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留百侧,地道東北人砰识。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像移层,于是被迫代替她去往敵國和親仍翰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,665評論 2 354