1. 程式人生 > >React v15到v16.3, v16.4新生命週期總結以及使用場景

React v15到v16.3, v16.4新生命週期總結以及使用場景

React

前言

真正在專案中用到React,正是React版本從15到16.3(專案當前使用版本)又到16.4的變化期。React從15到16最令人困惑的改變莫過於生命週期函式的劇烈變動,由此引發的一些新的實踐方法。事實上,我們專案在從15升級到16,在生命週期函式這塊可以說是新舊混雜,作為新來者難免會有很多困惑。我一直有個想法就是在接下來新功能的開發中儘量使用React16新推出的功能以及新周期函式,同時在開發中涉及到先前的程式碼時做一些優化,使程式碼結構更加清晰易讀。所以感覺很有必要把React從15到16生命週期函式的演化,原因以及如何使用做一個整理總結。

目錄結構大致如下:

  • 從React 15 到 16 預廢棄的生命週期以及廢棄原因
    • 廢棄生命週期
    • 遷移路徑:UNSAFE_XXX
    • 廢棄原因:async rendering in React 17
  • React 16 生命週期使用小結
    • getDerivedStateFromProps
      • 什麼是derived state
      • why static
      • 呼叫時機
      • 濫用根源
      • getDerivedStateFromProps vs componentWillReceiveProps
      • 總結以及使用場景
    • getSnapshotBeforeUpdate
      • 使用場景
      • getSnapshotBeforeUpdate vs componentWillUpdate
    • 其他生命週期
      • componentDidMount
      • componentDidUpdate
      • shouldComponentUpdate

React 15 到 16 生命週期函式變動

預廢棄:

componentWillMount,componentWillUpdate,componentWillReceiveProps

已新增:

static getDerivedStateFromProps(props, state)
getSnapshotBeforeUpdate(prevProps, prevState)

UNSAFE_componentWillMount
UNSAFE_componentWillReceiveProps
UNSAFE_componentWillUpdate

遷移路徑

目前新增了三個生命週期,其實也就是在預廢棄的周期函式前加一個USAFE_字首。React今後版本的釋出會逐步淘汰componentWillMount,componentWillReceiveProps 以及 componentWillUpdate
廢棄周期函式遷移路徑

根據遷移路徑,我的建議是:
1. 如果專案中React 版本已經是16.3及以上,在新程式碼中不要用被廢棄(deprecated)或者標位UNSAFE的生命週期函式。
2. 在開發中如果要修改已有程式碼,可以順便把就程式碼中的預廢棄的周期函式更改為新周期函式,當然前提是要評估好風險。

UNSAFE 周期函式廢棄或不推薦原因

關鍵字:Async Render

到目前為止(React 16.4),React的渲染機制遵循同步渲染:
1) 首次渲染: willMount > render > didMount,
2) props更新時: receiveProps > shouldUpdate > willUpdate > render > didUpdate
3) state更新時: shouldUpdate > willUpdate > render > didUpdate
3) 解除安裝時: willUnmount

期間每個周期函式各司其職,輸入輸出都是可預測,一路下來很順暢。

BUTReact 17 開始,渲染機制將會發生顛覆性改變,這個新方式就是 Async Render

首先,async render不是那種服務端渲染,比如發非同步請求到後臺返回newState甚至新的html,這裡的async render還是限制在React作為一個View框架的View層本身

通過進一步觀察可以發現,預廢棄的三個生命週期函式都發生在虛擬dom的構建期間,也就是render之前。在將來的React 17中,在dom真正render之前,React中的排程機制可能會不定期的去檢視有沒有更高優先順序的任務,如果有,就打斷當前的週期執行函式(哪怕已經執行了一半),等高優先順序任務完成,再回來重新執行之前被打斷的周期函式。這種新機制對現存周期函式的影響就是它們的呼叫時機變的複雜而不可預測,這也就是為什麼”UNSAFE”

從框架的穩定性考慮,不重寫現存的很成熟但不適應新機制的周期函式,另起爐灶為async render重新打造一套新周期函式也在情理之中。

React 16 生命週期使用小結

React16.4生命週期

static getDerivedStateFromProps(props, state)

這是一個我重點關注的周期函式,某種意義,它是作為componentWillReceiveProps的代替品出現,而在React 16 之前componentWillReceiveProps是一個最容易被濫用(misuse)的周期函式。所以很有必要好好研究一下getDerivedStateFromProps。

說句老實話,React 16之前的周期函式的命名非常語義化,willUpdate && didUpdate, willMount && didMount,哪怕沒有做過React,根據周期函式的名稱,也大概知道React的基本流程。但是從16以後,willMount, willUpdate都要被扔掉了,新增的周期函式與保留的週期從語義上很難將渲染流程連貫下來。

就getDerivedStateFromProps而言,我看到這個函式時馬上有兩個問題:
1. 什麼叫 Derived State
2. 這個函式為什麼是static

什麼叫 Derived State

getDerivedStateFromProps exists for only one purpose. It enables a component to update its internal state as the result of changes in props.

由於元件的props改變而引發了state改變,這個state就是derived state. derived from props. 舉個栗子:一個展示使用者聯絡人列表的控制元件:

// Parent Component
.....
render() {
    return <EmailList userid={id}/>
}
....
// Component EmailList
// 隨父元件傳入的props userid的不同而變化的state friends就是一個derived state
EmailList extends Component {
    constructor() {
        super(props);
        this.state = {friends: []}
    }
    componentDidMount() {
        loadAsyncList(this.props.userid)
            .then(data => this.setState({friends: data.list}));
    }
}

Why static

static 是ES6的寫法,當我們定義一個函式為static時,就意味著無法通過this呼叫我們在類中定義的方法(原理和js中原型鏈繼承相關,具體我就不說了,可自行搜尋)。

再來看函式引數:static getDerivedStateFromProps(nextProps, prevState)

通過static的寫法和函式引數,可以感覺React在和我說:請根據newProps來設定derived state,不要通過this這些東西來呼叫幫助方法,可能會越幫越亂。用專業術語說:getDerivedStateFromProps應該是個純函式,沒有副作用(side effect)

getDerivedStateFromProps呼叫時機

Mounting 時:無論是16.3還是16.4,都會觸發。

Updating 時
React 16.3: 只有props改變,才會呼叫這個周期函式來更新state 正確
React 16.4: 只有props改變,才會呼叫這個周期函式來更新state. 錯誤
事實上,在16.4中,在任何一次render前,getDerivedStateFromProps都會被觸發。這其中包括:
1. new props. 2. setState 3. forceUpdate

下面是一張對比圖,可以加深印象:
React 16.3
React 16.4

通過以上敘述,在16.4中,我們可以得出一條實踐:

在getDerivedStateFromProps中,在條件限制下(if/else)呼叫setState如果不設任何條件setState,這個函式超高的呼叫頻率,不停的setState,會導致頻繁的重繪,既有可能產生效能問題,同時也容易產生bug。

derived state 濫用根源(一):controlled vs uncontrolled

controlled和uncontrolled這兩個詞經常用於描述form,如果我們用state來控制form中各種input,那麼這個from就是controlled。

但是此處描述的controlled和uncontrolled是站在父元件的角度來看子元件。

controlled component: 子元件沒有state(有state意味著component可以通過setState來control自身的渲染),他的一切行為完全由父元件決定,因此是可控的controlled。

// 自己感受一下。連onChange這種事情都要爸爸代勞!
function EmailInput(props) {
  return <input onChange={props.onChange} value={props.email} />;
}

uncontrolled component: 子元件自身有internal state,不受父元件控制,自己玩自己的,因此是uncontrolled。

有個奇特存在就是derived state,它和父元件傳入的props聯動,從性質說又是子元件的state,可以通過setState來設定,那麼在這種情況下,controlled還是uncontrolled就很難。

derived state 濫用根源(二):本不需要你

最近兩天我自己在專案中遇到一個小問題很有意思,案例非常簡單:

後臺有一張記錄上傳資料資訊的表,表字段是表名和來源。
前臺有兩個聯動控制元件:
1)一個搜尋框,當用戶輸入字元時後臺會用模糊匹配返回符合關鍵字的資料表名。
2)資料表來源,預設是全部。切換來源會重置(reset)搜尋框。(包括清空搜尋框中的使用者輸入以及根據來源重新獲取搜尋框下拉列表中的結果)。

case1

我的第一反應當然是:我靠,這不正是一個典型的derived state嗎?我在父元件新增一個resetSearch的state,預設為false,作為props傳入子元件,當用戶改變資料來源時,將resetSearch置為true,這時子元件中的getDerivedStateFromProps被觸發,進行搜尋框清理工作。

首先宣告,這麼做是沒有問題的,我最初也是這樣實現的,要點如下:

// 父元件
class DataList extends Component {
    constructor(props) {
        super(props);
        this.state = {
            resetSearch: false,
            source: '全部'
        }
    }
    setSource = (source) => {
        this.setState({
            source: parseInt(source),
            resetSearch: true
        }, () => {
            // 遠端獲取新的資料表名
            this.getData();
            // resetSearch重置為false以備下次呼叫
            this.setState({resetSearch: false});
        });
    };

    render() {
        ......
        表名:
        <Search afterChange={this.afterSearchChange}>
        來源: 
        <Select defaultValue={this.state.source} onChange={this.setSource}>
               { options }
        </Select>
    }
}
// 子元件
class Search extends Component {
    static getDerivedStateFromProps(props, state) {
        if (props.resetSearch) {
            return {
                .... //相關state
            }
        }
    }
}

儘管可以用,但是邏輯還是挺麻煩的,兩邊元件的邏輯都要做一些相應的處理(比如父元件中將resetSearch設為true後,馬上又要設定回false,不然會有bug)。為一個不大的小功能改一堆東西挺不爽的。

我是看到官網中下面這段標紅的話意識到我也可能是濫用’derived state’和getDerivedStateFromProps周期函式了。

根據官網給出的alternative,我為元件加了一個key,它的值就是父元件中資料來源source的值。這樣當父元件中source切換時,的key值也會變化,這樣這個元件就會被銷燬並重新render,客觀上達到了reset的效果。簡單又明瞭!

// 父元件中移除resetSearch state,為<Search>加key
<Search key = {this.state.source}>

//子元件中移除getDerivedStateFromProps

getDerivedStateFromProps vs componentWillReceiveProps

replacement

儘管getDerivedStateFromProps 推出是作為 componentWillReceiveProps的‘安全‘版本,但是兩者的觸發還是有些不同。

在16.4中,getDerivedStateFromProps更全能,無論是mounting還是updating都會被觸發。componentWillReceiveProps只會updating階段,並且是父元件觸發的render才被呼叫。

getDerivedStateFromProps 使用場景

getDerivedStateFromProps被React官方歸類為不常用的生命週期,能不用就儘量不用,前面用那麼多篇幅講這個生命週期主要是為了加深對Reac執行機制的理解。

getDerivedStateFromProps Summary

備註:最後一條正是我之前講的關於reset的案例。

getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate 是在render之後觸發,它的要點在於觸發時,Dom還沒有更新,開發者可以做一些事情,返回值會作為第三個引數傳遞給接下來將要觸發的componentDidUpdate。

getSnapshotBeforeUpdate vs componentWillUpdate

可以把getSnapshotBeforeUpdate視作componentWillUpdate的“安全“版。。在componentWillUpdate觸發時,Dom同樣也還沒有更新。

它們之間最大的不同還是觸發時機,componentWillUpdate在updating階段的render之前觸發。

componentWillUpdate

其實,兩者的使用經典場景其實是一樣的:在beforeUpdate中記錄“舊“dom的資訊作為snapshot。

再多說一句,componentWillUpdate所謂不安全是指在React 17版中的async render機制下,由於優先順序許可權,render之前觸發的componentWillUpdate可能會反覆呼叫,獲取到的一些“舊“dom的資訊不一定準確。

作為一個不常用的生命週期,getSnapshotBeforeUpdate React 16給的建議當然還是:能不用就儘量不要用

其他生命週期

再簡單總結一下其他15版本就有的但是常用的生命週期。簡單羅列幾個要點,以備快速翻看。

componentDidMount()

  1. render階段觸發一次,ajax呼叫可以放到這裡。
  2. 也可以setState,但是會額外觸發一次render,但是兩次render都發生在瀏覽器更新screen之前,所以使用者不會感受到state連續改變引發的跳屏,但是可能會影響performance。

componentDidUpdate(prevProps, prevState, snapshot)

  1. render階段不會觸發。updating階段觸發。
  2. 如果在這裡setState,應設定前置條件,否則會陷入無限迴圈(深有體會)。
  3. setState同樣會觸發額外渲染,與componentDidMount類似,儘管使用者不會感受到,但可能影響效能。

shouldComponentUpdate(nextProps, nextState)

  1. 根據官網,唯一意義就是進一步改善效能。
  2. 慎用,可用PureComponent替換。
  3. 如返回false,將不會觸發UNSAFE_componentWillUpdate(), render(), and componentDidUpdate()。

相關推薦

React v15到v16.3, v16.4生命週期總結以及使用場景

前言 真正在專案中用到React,正是React版本從15到16.3(專案當前使用版本)又到16.4的變化期。React從15到16最令人困惑的改變莫過於生命週期函式的劇烈變動,由此引發的一些新的實踐方法。事實上,我們專案在從15升級到16,在生命

React v16.3生命週期

變更的部分react v16.3終於出來了,最大的變動莫過於生命週期去掉了以下三個componentWillMountcomponentWillReceivePropscomponentWillUpdate同時為了彌補失去上面三個週期的不足又加了兩個static getDerivedStateFromProp

react生命週期API(3.0)及生命週期與定時器的用法;

react的定時器的呼叫必須採用元件生命週期函式去呼叫: 有關元件的生命週期,見菜鳥教程: http://www.runoob.com/react/react-component-life-cycle

React躬行記(4)——生命週期

  元件的生命週期(Life Cycle)包含三個階段:掛載(Mounting)、更新(Updating)和解除安裝(Unmounting),在每個階段都會有相應的回撥方法(也叫鉤子)可供選擇,從而能更好的控制組件的行為。 一、掛載   在這個階段,元件會完成它的首次渲染,先執行初始化,再被掛載到真實的D

React從0到1--元件生命週期

1、React 嚴格定義了元件的生命週期,生命週期可能會經歷如下三個過程裝載過程( Mount),也就是把元件第一次在 DOM 樹中渲染的過程;更新過程( Update ),當元件被重新渲染的過程;解除安裝過程( Unmount),元件從 DOM 中刪除的過程 。 2、裝載過程 我們先來看裝載過程,當元件

react入門筆記七 (元件的生命週期)

生命週期分三個狀態 mounting(元件掛載階段) updating(元件更新) unmounting(元件移除) props與state         生命週期分四個階段 建立階

JVAWEB學習(3) — Serlvet的生命週期

Servlet的生命週期 什麼是Servlet的生命週期 Servlet容器如何建立Servlet物件,如何對該物件進行初始化處理,如何呼叫該物件的方法拉處理請求,以及如何銷燬該物件的整個過程。 Servlet的宣告週期分成哪幾個階段 1. 例項化 什麼是

3、Bean的生命週期

1、資源定位(@ComponentScan掃描) → Bean定義(將Bean定義儲存到BeanDefinition例項中) → 釋出Bean定義(IOC容器裝在Bean定義) → 例項化(建立Bean的例項物件) → 依賴注入(@Autowired注入資源) → setBeanName方法

vue2.0專案實戰(4生命週期和鉤子函式詳解

最近的專案都使用vue2.0來開發,不得不說,vue真的非常好用,大大減少了專案的開發週期。在踩坑的過程中,因為對vue的生命週期不是特別瞭解,所以有時候會在幾個鉤子函式裡做一些事情,什麼時候做,在哪個函式裡做,我們不清楚。 下面來總結一下vue的生命週期。 vue生命週期簡介 咱們從上圖可以很明顯的看出

React Native入門——元件構成及生命週期簡介

剛開始接觸React Native開發的程式猿可能會拿著網上的例子和文件一頭霧水,畢竟不是像C語言有個main、Android有個OnCreate,iOS有個ViewDidLoad那樣,加上JavaScript語法的隨意性,讓很多人無從下手,本文主要介紹React Nati

React生命週期總結

生命週期可能會經歷如下三個過程: 裝載過程:把元件第一次在DOM樹中渲染的過程 更新過程:當元件被重新渲染的過程 解除安裝過程:元件從DOM中刪除的過程 裝載過程: 當元件第一次被渲染的時候,依次呼叫的函式如下: constructor

React.js掛載階段的元件生命週期

元件的掛載指的是將元件渲染並且構造 DOM 元素然後插入頁面的過程。這是一個從無到有的過程,React.js 提供一些生命週期函式可以給我們在這個過程中做一些操作。 React.js 將元件渲染,並且構造 DOM 元素然後塞入頁面的過程稱為元件的掛載。這一節我

Android核心技術-day06-05-Activity生命週期,應用場景

package com.gaozewen.lifecycle; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import an

最常見的生命週期總結

最常見的生命週期總結     preInit:在頁面生命週期的早期階段可以訪問的事件,在preInit事件後,將載入個性化資訊和頁主題。     Init:在所有的控制元件都已初始化,且已應用所有外觀設定後引發。使用該事件來讀取或初始化控制元件屬性。

Vue 生命週期總結與思考實驗

               生命週期函式就是 Vue 例項在某一個時間點自動執行的函式 先上圖,一步一步講解 建議邊看生命週期圖 邊看最下面的步驟一步一步的走,有不理解的地方看看總結。並且在事件中多實驗。

09-Python面向物件-物件的生命週期以及週期方法

學習地址: 撩課-Python大資料+人工智慧1 撩課-Python大資料+人工智慧2 撩課-Python大資料+人工智慧3 撩課-Python大資料+人工智慧4 撩課-Python大資料+人工智慧5 撩課-Python大資料+人工智慧6 撩課-Python-GUI程式設計-PyQt5

android Activity生命週期總結

是什麼? acntivity是一組包含使用者介面的元件,主要用於和使用者進行互動的。也就是使用者看得到的東西就是activity。 Activity是怎麼回退的: android是使用任務task去

Android之Activity生命週期總結(二)

     上一次寫了一下正常情況下的生命週期,這一次在這裡對異常狀態的生命週期進行一個個人的總結。       Activity除了使用者正常的操作所導致的生命週期方法的呼叫,但是還有一些極端的情況會導致Activity 生命週期  無法按照正常的情況去呼叫。下面具體的分析異

virsh命令虛擬機器生命週期管理以及獲取虛擬機器ip

一、virsh管理虛擬機器生命週期 virsh define domain.xml virsh start domain virsh vncdisplay domain virsh list --all virsh shutdown domain virsh reboot 

JavaScript是如何工作的:Service Worker的生命週期及使用場景

摘要: 理解Service Worker。 原文:JavaScript 是如何工作的:Service Worker 的生命週期及使用場景 作者:前端小智 Fundebug經授權轉載,版權歸原作者所有。 這是專門探索 JavaScript 及其所構建的元件的系列