簡介
React 高階元件簡單來說就是一個沒有副作用的高階純函式,且該函式接受一個元件作為引數,並返回一個新的元件。正式由於他也算是高階函式,所以也可以使用類似柯里化的方式傳元件或者其他引數。常用於多個元件有共同方法的時候。
1.基礎理解
假如現在有以下兩個元件,分別是對當前使用者進行說明
- 1.使用者簡介
// UserProfiles.js
import * as React from 'react';
import axios from 'axios';
class UserProfiles extends React.Component {
state = {
username: ''
}
componentWillMount() {
axios.get('/username').then(item => {
this.setState({
username: item.data.username
})
}).catch(() => {
console.log('error')
});// 獲取使用者名稱
}
render() {
return (
<div>{this.state.username} 是xxx院院士,中國高階工程師,巴拉巴拉</div>
)
}
}
export default UserProfiles;
複製程式碼
- 使用者退出
// goodbueuser.js
import * as React from 'react';
import axios from 'axios';
class UserProfiles extends React.Component {
state = {
username: ''
}
componentWillMount() {
axios.get('/username').then(item => {
this.setState({
username: item.data.username
})
}).catch(() => {
console.log('error')
});// 獲取使用者名稱
}
render() {
return (
<div>再見 {this.state.username}</div>
)
}
}
export default GoodbyeUser;
複製程式碼
可以看到程式碼的冗餘量。按照平時我們的編碼習慣,冗餘的程式碼我們會將它封裝成一個函式。高階元件就類似這麼一個函式的概念。好,下面我們嘗試用高階元件來對這兩個元件封裝
//hoc.js
import * as React from 'react'
import axios from 'axios';
export interface Iprops {
username: string;
}
export default <T extends React.Component<Iprops>>(Com: new (props: Iprops) => T) => {
class NewComponent extends React.Component {
state = {
username: ''
}
componentWillMount() {
axios.get('/username').then(item => {
this.setState({
username: item.data.username
})
}).catch(() => {
console.log('error')
});// 獲取使用者名稱
}
render() {
return <Com username={this.state.username} />
}
}
return NewComponent
}
}
複製程式碼
// UserProfiles.js
import React, { Component } from 'react';
import wrapHoc, { Iprops } from 'hoc';
class UserProfiles extends Component<Iprops> {
render() {
return (
<div>{this.props.username} 是xxx院院士,中國高階工程師,巴拉巴拉</div>
)
}
}
UserProfilesCom = wrapHoc(UserProfiles);
export default UserProfilesCom;
複製程式碼
// goodbueuser.js
import * as React from 'react';
import wrapHoc, { Iprops } from 'hoc';
class GoodbyeUser extends React.Component<Iprops> {
render() {
return (
<div>再見 {this.props.username}</div>
)
}
}
GoodbyeUserCom = wrapHoc(GoodbyeUser);
export default GoodbyeUserCom;
複製程式碼
看到沒有,高階元件就是把username通過props傳遞給目標元件了。目標元件只管從props裡面拿來用就好了。這樣子就完成了一個基本的高階元件了
2.更改props
可以對引數元件的props進行增刪改查的操作。我們平時使用的時候更多的是增加的操作,比如說我們常用的 react-router-dom中的Withrouter()。上面的例子就是屬於更改props
通過高階元件,我們可以在引數元件中使用this.props.username來呼叫這個應用的使用者名稱了,這就類似withrouter中使用this.props.match.params.xxxx,來獲取路由引數一樣。
3.抽象state
這種用法一般用於不可控元件。比如我們熟悉的input,在react中我們需要通過onChange事件將輸入的值繫結到state上,然後通過觸發render函式渲染UI才能體現出來。通過高階元件,我們可以抽象一個state,將不可控元件轉變成可控元件。
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}/>
}
}
}
// 連結:https://www.jianshu.com/p/0aae7d4d9bc1
複製程式碼
然後這樣使用它:(這裡作者使用高階元件的修飾器用法)
@ppHOC
class Example extends React.Component {
render() {
return <input name="name" {...this.props.name}/>
}
}
複製程式碼
4.更多高階函式用法
前面提到了,高階元件就是一個高階函式,那麼我們使用很多關於高階函式的用法。比如說柯里化
import React from 'react';
import axios from 'axios';
export interface Iprops {
data: string;
}
const HocCom = (key: string) => <T extends React.Component<Iprops>>(Com: new (props: Iprops) => T) => {
return class extends React.Component {
componentWillMount() {
axios.get('username/' + key).then(item => {
this.setState({
data: item.data.username
});
}).catch({
console.log('error')
})
}
render() {
return <Com data={this.state.data} />
}
}
}
class Com extends React.Component<Iprops> {
render() {
return <div>{this.props.data}</div>
}
}
export default HocCom('wenfei')(Com)
複製程式碼
我們常見的這種柯里化的模式就是在antd的表單模組中,我們一般都會在模組的結尾寫上
const WrappedNormalLoginForm = Form.create()(NormalLoginForm);
複製程式碼
5.高階元件使用注意事項
- 不要在元件的render方法中使用高階元件,儘量也不要在元件的其他生命週期方法中使用高階元件
- 如果需要使用被包裝元件的靜態方法,那麼必須手動拷貝這些靜態方法
6.高階元件、元件繼承的區別
高階元件、元件組合、元件繼承都可以用來擴充元件,賦予元件新的能力。 react的官網 (reactjs.org/docs/compos…) 簡單的說明了一下元件組合和繼承的形式區別,而且在文章末尾建議我們不要使用繼承的方式來擴充元件。為什麼呢?我認為有以下幾點。
1.靈活性
對於小專案,元件複用率小的情況下,其實三種方式都合適。但是對於大型的元件複用率高的專案,繼承就會出現和明顯的短板。比如說一個簡單的List元件,通過繼承方式獲得可以支援上/下拉載入的元件ListA。有一天,專案經理說需要講這個功能改成下拉重新整理和向下滾動載入,發現ListA有部分功能相似,然後通過ListA繼承修改程式碼獲得了下拉重新整理和向下滾動載入的元件ListB。第二天,專案經理又說要將功能做成僅僅滾動載入(例子,平時見不到這種需求)。what???我是要在原List元件上繼承呢?還是在ListB上來繼承呢?假如還有很多個list需要這種需求呢??都用來繼承?這顯得元件特別膨脹而且不靈活,耦合程度高。其實我們可以封裝對應list的不同功能的高階元件或元件組合,比如說下拉重新整理的,向下滾動載入的,等等...然後拿來即用,傳遞想要功能的引數即可。靈活性大大提高。
2.單一性
還是上面的例子,我們發現我們通過繼承獲得了List\ListA\ListB等等元件。我們封裝元件,一般是它實現了這個專案最基礎而且可用的某個功能,相同功能引用相同元件即可。而繼承的方式會獲得基於這個基礎元件的不同功能的元件,而且這些元件做的是同樣事情,僅僅在方式上的不同。高階元件/元件組合則可以保持這種react元件單一的特性。
3.複雜度
寫元件繼承,1.書寫的麻煩,每個繼承元件需要額外開銷許多import和export。2.繼承的時候還需要知道原元件實現了哪些方法?如果使用了ts還可能出現更多各種各樣的錯誤。3.初始化 state、宣告其它方法時要小心避免汙染父類的state與方法。
參考文章
作者簡介: 張栓,人和未來大資料前端工程師,專注於html/css/js的學習與開發。