react 高階函式詳解(附送彩蛋)

金亮亮發表於2019-03-03

此文適合react新手入門,react大佬可以略過(畢竟以前都是寫vue,React才寫了一個多月, 掩面淚奔)

主要是學習react中的一些經驗總結,如果你覺得對你有幫助,可以給個贊github

react專案入門

react版本:16.0.0 (因為工作中還是15的版本)

首先我們先來說說 有狀態元件和無狀態元件

有狀態元件 無狀態元件

有狀態元件:元件內部狀態發生變化,需要state來儲存變化。

無狀態元件:元件內部狀態是不變的,用不到state。建議寫成函式元件

元件設計思路:通過定義少部分有狀態元件管理整個應用的狀態變化,並且將狀態通過props傳遞給其餘無狀態元件。

有狀態元件主要關注處理狀態變化的業務邏輯,無狀態元件主要關注元件UI渲染工作。這樣更有利於元件的複用,元件之間解耦更加徹底

這樣有時就會產生一個問題,如果給UI元件加不同的邏輯怎麼辦?

2種比較好的方法,
this.props.children 和 HOC ,
具體例項。那麼下面就來詳細說說 HOC 高階元件

知識前置:

裝飾器設計模式:允許向一個現有的物件新增新的功能,同時又不改變其結構。這種型別的設計模式屬於結構型模式,它是作為現有的類的一個包裝。

這種模式建立了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。

HOC 高階元件 (封裝並分離元件的通用邏輯。其實就是裝飾器設計模式的應用)

  • 基本概念:

在JS中高階函式可以接受一個函式作為引數,返回值作為也是函式的函式。類似的 高階元件也可以接受一個元件為引數,返回一個被加工過的元件。

本質上高階函式是一個函式,而不是元件。

  • 使用場景:
  1. 操縱props
  2. 通過ref訪問元件的例項
  3. 元件狀態提升
  4. 用其他元素包裝元件

例子:

  1. 操縱props 這個用的比較多

在被包裝的元件接收到props之前。高階元件可以先攔截到props,對props執行增刪改等操作,然後將修改過的props傳給被包裝元件。

import React, { Component } from `react`

function withPersistentData (wrapedComponent) {
  return class extends Component {
    componentWillMount() {
      let data = localStore.getItem(`data`);
      this.setState({data})
    }

    render () {
      const { data } = this.state;
      // 通過{...this.props} 把傳給當前元件的值繼續傳給被包裝的元件
      return <wrapedComponent data={data} {...this.props} />
    }
  }
}

@withPersistentData
export default class myComponent extends Component {
  render() {
    return (
      <div>
        {this.props.data}
      </div>
    )
  }
}

複製程式碼
  1. 通過ref訪問元件的例項。這個用法我是比較少用到。

高階元件通過ref獲取被包裝的元件例項的引用,然後高階元件就具備了直接操作被包裝元件的屬性和方法的能力。

function withRef (wrapedComponent) {
  return class extends Component {
    someMethod = () => {
      // this.wrapedComp        被包裝元件例項 
      // someMethodWrapedComp   被包裝元件的方法
      this.wrapedComp.someMethodWrapedComp()
    }

    render () {
      // 通過{...this.props} 把傳給當前元件的值繼續傳給被包裝的元件
      // 給被包裝的元件新增ref屬性,獲取被包裝元件例項並賦值給this.wrapedComp
      return <wrapedComponent ref={(comp) =>{this.wrapedComp = comp}} {...this.props} />
    }
  }
}
複製程式碼
  1. 元件狀態提升

上面已經說過 無狀態元件更容易被複用,我們可以利用高階元件將原本受控元件中需要自己維護的的狀態統一提升到高階元件中,受控元件無狀態化。

import React, { Component } from `react`

function withControlledComp(wrappedComp) {
  state = {
    value : null,
  }
  handleValueChange = (e) => {
    this,setState({value: e.target.value})
  }

  render () {
    const newProps ={
      controlledProps: {
        value: this.state.value,
        onChange: this.handleValueChange
      }
    }
    return <wrappedComp {...this.props} {...newProps} />
  }
}

@withControlledComp
class ControlledComp extends Component {
  render () {
    // 此時的受控元件為無狀態元件,狀態由高階元件控制
    return <input {...this.props.controlledProps} />
  }
}
複製程式碼
  1. 用其他元素包裝元件
function withRedColor (wrapedComponent) {
  return class extends Component {
    render () {
      return (<div style={color: `red}><wrapedComponent {...this.props} /> </div>)
    }
  }
}
複製程式碼

* 引數傳遞
高階元件的引數除了接受元件,還可以接受其他引數。

在第一個操作props的例子裡,如果要獲取key值不確定時,這個元件就不滿足了。

我們一般採用這種方式:HOC(…params)(wrappedComp)

function withPersistentData = (key) => (wrapedComponent) => {
  return class extends Component {
    componentWillMount() {
      let data = localStore.getItem(key);
      this.setState({data})
    }

    render () {
      const { data } = this.state;
      // 通過{...this.props} 把傳給當前元件的值繼續傳給被包裝的元件
      return <wrapedComponent data={data} {...this.props} />
    }
  }
}

class myComponent extends Component {
  render() {
    return (
      <div>
        {this.props.data}
      </div>
    )
  }
}

// 獲取key=‘data’的資料
const myComponentWithData = withPersistentData(`data`)(myComponent)

// 獲取key=‘name’的資料
const myComponentWithData = withPersistentData(`name`)(myComponent)
複製程式碼

實際上這種形式的高階元件大量出現在第三方的庫中,例如react-redux中的connect函式

connect(mapStateToProps, mapDispatchToProps)(wrappedComponent)
複製程式碼

* 注意事項

  1. 不要在render中使用高階元件,也儘量不要在其他的生命週期函式中使用高階元件。 因為高階元件每次返回的都是一個新元件,於是每次render,前一次建立的元件都會被解除安裝,本次建立的元件會被重新掛載。
  2. 如果需要使用被包裝元件的靜態方法,就必須要手動複製這些方法。因為高階元件不包含被包裝元件的靜態方法。
  3. Refs不回被傳遞給被包裝元件。
  4. 與父元件的區別。如果這部分邏輯與UI/DOM相關,那麼這部門邏輯適合放在父元件中實現;如果邏輯與DOM不直接相關,那麼這部分邏輯適合放在高階元件的抽象中。例如資料校驗請求傳送等

彩蛋來了,彩蛋就是react生命週期的使用場景,哈哈哈哈哈,驚不驚喜,意不意外。

react元件 的生命週期 以及 使用場景

掛載階段

1. constructor
2. componentWillMount
3. render
4. componentDidMount
複製程式碼

使用場景

1. coustructor  
  通常用於初始化元件的state以及繫結事件的處理方法(比如bind(this))等

2. componentWiillMound
  在元件被掛載到DOM前呼叫,且只會呼叫一次,
  實際專案中比較少用到,因為可以在該方法中的執行的都可以提到coustructor中
  在這個方法中this.setState不會引起重新渲染

3. render
  渲染方法。
  注意:render只是返回一個UI的描述,真正渲染出頁面DOM的工作由react自己完成
  
4. componentDidMount
  在元件被掛載到DOM後呼叫,且只會呼叫一次,
  通常用於像後端請求資料
  在這個方法中this.setState會引起元件的重新渲染

複製程式碼

更新階段

1. componentWillReceiveProps
2. shouldComponentUpdate
3. componentWillUpdate
4. render
5. componentDidUpdate
複製程式碼

使用場景

1. componentWillRceiveProps(nextProps)
  這個方法只在props引起元件更新時呼叫。
  一般會比較一下this.props和nextProps來決定是否執行props變化後的邏輯
  比如:根據新的props呼叫this.setState來觸發元件的重新渲染

2. shouldComponentUpdate(nextProps,nextState)
  這個方法決定元件是否繼續執行更新過程。 
  預設是true,繼續更新;false阻止更新。
  一般是通過比較nextPops,nextState和當前元件的props,state來決定返回結果。 
  這個方法可以減少不必要的渲染,優化元件效能。
  原因:根據渲染流程,首先會判斷shouldComponentUpdate是否需要更新。如果需要更新,呼叫render方法生成新的虛擬DOM與舊的虛擬DOM進行對比(render只是返回一個UI描述),如果對比不一致,則根據最小粒度改變去更新DOM。

3. componentWillUpdate
  render前呼叫,元件更新前執行某些邏輯的地方。
  一般很少用到。

4. render

5. componentDidUpdate(prevProps, prevState)
  元件更新後被呼叫,可以作為操作更新後DOM的地方。
  這個方法中的prevProps和prevState代表元件中更新前的props和state。

注意:在render前的生命週期中,componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate中的this.state依然指向更新前的state。
複製程式碼

銷燬階段

componentWillUnmount
  元件解除安裝前被呼叫。
  清除定時器,清除componentDidMount中手動建立的DOM,取消請求等
複製程式碼

結束語

最後感謝能看到這裡的朋友,因為水平有限,如果有錯誤敬請指正,十分感激。

參考:
react進階之路

相關文章