本文章重點講述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)