React高階元件入門與常用用法

genetalks_大資料發表於2019-04-04

簡介

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與方法。

參考文章

www.jianshu.com/p/0aae7d4d9…


作者簡介: 張栓,人和未來大資料前端工程師,專注於html/css/js的學習與開發。

相關文章