React16時代,該用什麼姿勢寫 React ?

小賊先生_ronffy發表於2019-02-28

React16 後的各功能點是多個版本陸陸續續迭代增加的,本篇文章的講解是建立在 16.6.0 版本上 本篇文章主旨在介紹 React16 之後版本中新增或修改的地方,所以對於 React16 之前版本的功能,本篇文章當作您已充分了解了,不再贅述

更新概覽

從 React v16.0 ~ React v16.6 的更新概覽(只涉及部分常用api):

  • React v16.0
  1. render支援返回陣列和字串
  2. 支援自定義 DOM 屬性
  3. 減少檔案體積
  • React v16.3
  1. createContext
  2. createRef
  3. 生命週期函式的更新
  • React v16.4

更新 getDerivedStateFromProps

  • React v16.6
  1. memo
  2. lazy
  3. Suspense
  4. static contextType
  5. static getDerivedStateFromError
  • React v16.7(~Q1 2019)

Hooks

接下來將針對影響較大,使用頻率較高的更新點逐一講解。

純函式的PureComponent

我們知道,對 React 元件的效能優化,shouldComponentUpdate函式是很重要的一啪,所以 React 才會在 React.Component的基礎上增加了React.PureComponent,但是對於非class類的純函式寫法,卻沒法增加這樣的便捷處理。 對於這個問題,React16.6 增加了React.memo這個高階元件

一般使用方式:

const C = React.memo(props => {
  // xxx
})
複製程式碼

React.memo的實現類似React.PureComponent,所以它內部是對物件進行淺比較。 React.memo允許你自定義比較方法,如下:

// 函式的返回值為 true 時則更新元件,反之則不更新
const equalMethod = (prevProps, nextProps): boolean => {
  // 定義你的比較邏輯
}
const C = React.memo(props => {
  // xxx
}, equalMethod)
複製程式碼

新的生命週期函式是怎樣的

React生命週期分為三個階段:掛載、更新、解除安裝,React16後又多了一個異常,我們一一看下。

React16時代,該用什麼姿勢寫 React ?
[更新圖片,此處特別感謝 @wuzhengyan2015 指正]

掛載

生命週期的執行順序

  1. constructor
  2. static getDerivedStateFromProps
  3. render
  4. componentDidMount

rendercomponentDidMount較 React16 之前無變化。對於掛載過程,我們著重看下constructorcomponentWillMountstatic getDerivedStateFromProps

constructor

  1. 初始化 state
    注意:應避免使用propsstate賦值,這樣的話, state的初始化可以提到constructor外面處理
constructor(props) {
  super(props);
  this.state = {
    x: 1,
    // y: props.y, // 避免這樣做,後面我們會講應該怎樣處理
  }
}
複製程式碼
  1. 給方法繫結this
constructor(props) {
  super(props);
  this.handleClick = this.handleClick.bind(this);
}
複製程式碼

但是,以上兩件事放到constructor外面處理會更簡單些,如下:

class C extends React.Component {
  state = {
    x: 1
  }
  handleClick = (e) => {
    // xxx
  }
}
複製程式碼

所以,React16 以後用到constructor的場景會變少。

componentWillMount

可以看到,componentWillMount在 React16 中被“刪掉”了(這樣說其實是有問題的,因為 React 並未真正刪除該生命週期函式,只是告誡開發者,該函式在未來版本中會被廢棄掉),那麼問題就出現了,原先在這個生命週期中的做的事情,現在該放到哪裡去做呢?

首先問自己一個問題,原先的時候都在這個生命週期裡做什麼?答案是大部分時候會在這裡做 AJAX 請求,然後執行setState重新渲染。

然而在componentWillMount裡做 AJAX 請求實在不是一個明智之舉,因為對於同構專案中,componentWillMount是會被呼叫的。

還有人會在這裡面初始化state,關於state的初始化,請參看樓上小節。

綜上所述,componentWillMount其實本來沒有什麼主要作用,如果你的程式碼規範,去掉的話,不會對現在的專案產生什麼影響。

static getDerivedStateFromProps

上面我們講到,應避免使用propsstate賦值,但是在 React16 前我們都是這麼做的,現在如果不讓這麼操作了,那該在哪裡處理這塊邏輯呢? React16 給出的答案就是 static getDerivedStateFromProps
掛載元件時,該靜態方法會在render前執行;更新元件時,該靜態方法會在shouldComponentUpdate前執行。

class C extends React.Component {
  state = {
    y: 0
  }
  static getDerivedStateFromProps(props, state): State {
    if(props.y !== state.y) {
      return {
        y: props.y
      };
    }
  }
}
複製程式碼

getDerivedStateFromProps的返回值將作為setState的引數,如果返回null,則不更新state,不能返回object 或 null 以外的值,否則會警告。

getDerivedStateFromProps是一個靜態方法,是拿不到例項this的,所以開發者應該將該函式設計成純函式。

這樣,有沒有發現componentWillReceiveProps也就沒有用武之地了?是的,React16 把它也“刪掉”了(這樣說其實是有問題的,因為 react 並未真正刪除該生命週期函式,只是告誡開發者,該函式在未來版本中會被廢棄掉,建議使用更好的getSnapshotBeforeUpdategetDerivedStateFromProps

更新

生命週期函式的執行順序

  1. static getDerivedStateFromProps
  2. shouldComponentUpdate
  3. render
  4. getSnapshotBeforeUpdate
  5. componentDidUpdate

static getDerivedStateFromProps前面已經介紹過了,而其他的幾個生命週期函式與 React16 之前基本無異,所以這裡主要介紹下getSnapshotBeforeUpdate

getSnapshotBeforeUpdate

在 React 更新 DOM 之前呼叫,此時state已更新; 返回值作為componentDidUpdate的第3個引數; 一般用於獲取render之前的 DOM 資料

語法:

class C extends React.Component {
  getSnapshotBeforeUpdate (prevProps, prevState): Snapshot {
    
  }
  componentDidUpdate(prevProps, prevState, snapshot) {
    // snapshot 是從 getSnapshotBeforeUpdate 的返回值,預設是 null
  }
}
複製程式碼

getSnapshotBeforeUpdate 的使用場景一般是獲取組建更新之前的滾動條位置。

解除安裝

componentWillUnmount

較之前無變化。

異常

componentDidCatch 這個函式是 React16 新增的,用於捕獲元件樹的異常,如果render()函式丟擲錯誤,則會觸發該函式。可以按照 try catch 來理解和使用,在可能出現錯誤的地方,使用封裝好的包含 componentDidCatch 生命週期的組建包裹可能出錯的元件。

class PotentialError extends React.Component {
  state = {
    error: false,
  }
  componentDidCatch(error, info) {
    console.error(info);
    this.setState({
      error
    });
  }
  render() {
    if (this.state.error) {
      return <h1>出錯了,請打卡控制檯檢視詳細錯誤!</h1>;
    }
    return this.props.children;   
  } 
}
複製程式碼

如:

const Demo = () => (
  <PotentialError>
    <div>{{a: 1}}</div>
  </PotentialError>
)
複製程式碼

這樣,Demo 元件即使直接使用物件作為子元件也不會報錯了,因為被 PotentialError 接收了。

新生命週期的完整demo

看看穿上新生命週期這身新衣服後的樣子吧

import React from 'react'

export default class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    // 初始化state方式(1)
    this.state = {

    }
  }
    
  static defaultProps = {

  }

  // 初始化state方式(2)
  state = {

  }
  static getDerivedStateFromProps(props, state) {
    return state
  }
  componentDidCatch(error, info) {

  }
  render() {

  }
  componentDidMount() {

  }
  shouldComponentUpdate(nextProps, nextState) {
    
  }
  getSnapshotBeforeUpdate(prevProps, prevState) {

  }
  componentDidUpdate(prevProps, prevState, snapshot) {

  }
  componentWillUnmount() {

  }

}
複製程式碼

Suspense

Hooks

time slicing

【未完待續】

相關文章