react

React 生命週期

一個React元件的生命週期分為三個部分:例項化、存在期和銷燬時。

>

一個React元件的生命週期分為三個部分:例項化、存在期和銷燬時。

例項化階段

  1. 客戶端渲染時,如下依次被呼叫

    • getDefaultProps()
    • getInitialState()
    • componentWillMount()
    • render()
    • componentDidMount()
  2. 服務端渲染

    • getDefaultProps()
    • getInitialState()
    • componentWillMount()
    • render()

注意:componentDidMount()不會再服務端被渲染;

getDefaultProps

對於每個元件例項來講,這個方法只會呼叫一次,該元件類的所有後續應用,getDefaultPops 將不會再被呼叫,其返回的物件可以用於設定預設的props值。

var Hello = React.creatClass({
    getDefaultProps: function(){
        return {
            name: 'pomy',
            git: 'dwqs'
        }
    },
    
    render: function(){
        return (
            <div>Hello,{this.props.name},git username is {this.props.dwqs}</div>
        )
    }
});

ReactDOM.render(<Hello />, document.body);

也可以在掛載元件的時候設定 props。

var data = [{title: 'Hello'}];
<Hello data={data} />

或者呼叫 setProps (一般不需要呼叫)來設定其 props

var data = [{title: 'Hello'}];
var Hello = React.render(<Demo />, document.body);
Hello.setProps({data:data});

但只能在子元件或元件樹上呼叫 setProps。別調用 this.setProps 或者 直接修改 this.props。將其當做只讀資料。

React通過 propTypes 提供了一種驗證 props 的方式,propTypes 是一個配置物件,用於定義屬性型別:

var survey = React.createClass({
    propTypes: {
        survey: React.PropTypes.shape({
            id: React.PropTypes.number.isRequired
        }).isRequired,
        onClick: React.PropTypes.func,
        name: React.PropTypes.string,
        score: React.PropTypes.array
        ...
    },
    
    //...
})

或者

import React, { Component } from 'react'
import PropTypes from 'prop-types'

class BetterImage extends Component{...}

BetterImage.PropTypes={
  src: PropTypes.string,
  center: PropTypes.bool,
  loadingImage: PropTypes.string,
  defaultImage: PropTypes.string,
  onLoad: PropTypes.func,
  onError: PropTypes.func,
  onComplete: PropTypes.func
}
BetterImage.defaultProps={
  ....
}

getInitialState

對於元件的每個例項來說,這個方法的呼叫有且只有一次,用來初始化每個例項的 state,在這個方法裡,可以訪問元件的 props。每一個React元件都有自己的 state,其與 props 的區別在於 state只存在元件的內部,props 在所有例項中共享。

getInitialState 和 getDefaultPops 的呼叫是有區別的,getDefaultPops 是對於元件類來說只調用一次,後續該類的應用都不會被呼叫,而 getInitialState 是對於每個元件例項來講都會呼叫,並且只調一次。

var LikeButton = React.createClass({
  //初始化State
  getInitialState: function() {
    return {liked: false};
  },
  
  handleClick: function(event) {
    //設定修改State
    this.setState({liked: !this.state.liked});
  },

  render: function() {
    var text = this.state.liked ? 'like' : 'haven\'t liked';
    return (
      <p onClick={this.handleClick}>
        You {text} this. Click to toggle.
      </p>
    );
  }
});

ReactDOM.render(
  <LikeButton />,
  document.getElementById('example')
);

每次修改 state,都會重新渲染元件,例項化後通過 state 更新元件,會依次呼叫下列方法:

1、shouldComponentUpdate
2、componentWillUpdate
3、render
4、componentDidUpdate
  • componentWillMount

在渲染前呼叫,在客戶端也在服務端。React 官方正式釋出了 v16.3 版本。在這次的更新中,除了前段時間被熱烈討論的新 Context API 之外,新引入的兩個生命週期函式 getDerivedStateFromProps,getSnapshotBeforeUpdate 以及在未來 v17.0 版本中即將被移除的三個生命週期函式 componentWillMount,componentWillReceiveProps,componentWillUpdate .

在這個生命週期中你會遇到一下問題:

a.首屏無資料導致白屏

在 React 應用中,許多開發者爲了避免第一次渲染時頁面因為沒有獲取到非同步資料導致的白屏,而將資料請求部分的程式碼放在了 componentWillMount 中,希望可以避免白屏並提早非同步請求的傳送時間。但事實上在 componentWillMount 執行後,第一次渲染就已經開始了,所以如果在 componentWillMount 執行時還沒有獲取到非同步資料的話,頁面首次渲染時也仍然會處於沒有非同步資料的狀態。換句話說,元件在首次渲染時總是會處於沒有非同步資料的狀態,所以不論在哪裏傳送資料請求,都無法直接解決這一問題。而關於提早傳送資料請求,官方也鼓勵將資料請求部分的程式碼放在元件的 constructor 中,而不是 componentWillMount。

若是爲了改善使用者體驗曾經用過的解決方法有兩個:

方法一:非同步請求元件,使用nprogress 新增載入動畫;

import React, { Component } from 'react'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import './customNprogress.styl'

NProgress.configure({ showSpinner: false })

export default function asyncComponent(importComponent) {
  class AsyncComponent extends Component {
    state = {
      component: null
    }

    async componentDidMount() {
      NProgress.start()
      const { default: component } = await importComponent()
      NProgress.done()

      this.setState({ component })
    }

    render() {
      const C = this.state.component

      return C ? <C {...this.props} /> : null
    }
  }

  return AsyncComponent
}

const AsyncNotFound = asyncComponent(() => import(/* webpackChunkName: "NotFound" */ '@/routes/NotFound'))

方法二:使用 onreadystatechange 去監聽 readyState,在資源載入完成之前載入一個只有框架的靜態頁面,頁面不請求資料。當資料請求完成之後再將路由切換到真實的首頁。

function listen () {
    if (document.readyState == 'complete') { // 資源載入完成
        ReactDom.render(
            <Provider store={store}>
                <Router>
                    <Route path="/" component={Index}/>
                </Router>
            </Provider>,
            document.getElementById('root')
        )
    } else { // 資源載入中
        ReactDom.render(
            <Provider store={store}>
                <Router>
                    <Route path="/" component={FirstScreen}/>
                </Router>
            </Provider>,
            document.getElementById('root')
        )
    }
}

document.onreadystatechange = listen

b.事件訂閱

另一個常見的用例是在 componentWillMount 中訂閱事件,並在 componentWillUnmount 中取消掉相應的事件訂閱。但事實上 React 並不能夠保證在 componentWillMount 被呼叫後,同一組件的 componentWillUnmount 也一定會被呼叫。一個當前版本的例子如服務端渲染時,componentWillUnmount 是不會在服務端被呼叫的,所以在 componentWillMount 中訂閱事件就會直接導致服務端的記憶體洩漏。另一方面,在未來 React 開啟非同步渲染模式後,在 componentWillMount 被呼叫之後,元件的渲染也很有可能會被其他的事務所打斷,導致 componentWillUnmount 不會被呼叫。而 componentDidMount 就不存在這個問題,在 componentDidMount 被呼叫後,componentWillUnmount 一定會隨後被呼叫到,並根據具體程式碼清除掉元件中存在的事件訂閱。

render

該方法會建立一個虛擬DOM,用來表示元件的輸出。對於一個元件來講,render方法是唯一一個必需的方法。render方法需要滿足下面幾點:

  • 只能通過 this.props 和 this.state 訪問資料(不能修改)
  • 可以返回 null,false(這種場景下,react渲染一個
  • 只能出現一個頂級元件,不能返回一組元素
  • 不能改變元件的狀態
  • 不能修改DOM的輸出

render方法返回的結果並不是真正的DOM元素,而是一個虛擬的表現,類似於一個DOM tree的結構的物件。react之所以效率高,就是這個原因。

render執行情況如下:

  1. 首次載入
  2. setState改變元件內部state。 注意: 此處是說通過setState方法改變。
  3. 接受到新的props

注意:因為資料是非同步的情況,會導致元件重複渲染

componentDidMount

該方法不會在服務端被渲染的過程中呼叫。該方法被呼叫時,已經渲染出真實的 DOM,可以再該方法中通過 this.getDOMNode() 訪問到真實的 DOM(推薦使用 ReactDOM.findDOMNode())。

var data = [..];
var comp = React.createClass({
    render: function(){
        return <imput .. />
    },
    componentDidMount: function(){
        $(this.getDOMNode()).autoComplete({
            src: data
        })
    }
})

由於元件並不是真實的 DOM 節點,而是存在於記憶體之中的一種數據結構,叫做虛擬 DOM (virtual DOM)。只有當它插入文件以後,纔會變成真實的 DOM 。有時需要從元件獲取真實 DOM 的節點,這時就要用到 ref 屬性:

var Area = React.createClass({
    render: function(){
        this.getDOMNode(); //render呼叫時,元件未掛載,這裏將報錯
        
        return <canvas ref='mainCanvas'>
    },
    componentDidMount: function(){
        var canvas = this.refs.mainCanvas.getDOMNode();
        //這是有效的,可以訪問到 Canvas 節點
    }
})

需要注意的是,由於 this.refs.[refName] 屬性獲取的是真實 DOM ,所以必須等到虛擬 DOM 插入文件以後,才能使用這個屬性,否則會報錯。如果ref回撥函式以inline函式的方式來指定,那麼在元件更新的時候ref回撥會被呼叫2次。第一次回撥的時候傳入的引數是null,而第二次的時候才真正的傳入DOM節點

獲取真實dom,並獲取dom css 三種方法

存在期

此時元件已經渲染好並且使用者可以與它進行互動,比如滑鼠點選,手指點按,或者其它的一些事件,導致應用狀態的改變,你將會看到下面的方法依次被呼叫;

  1. componentWillReceiveProps()
  2. shouldComponentUpdate()
  3. componentWillUpdate()
  4. render()
  5. componentDidUpdate()

componentWillReceiveProps

當props發生變化時執行,初始化render時不執行,在這個回撥函式裏面,你可以根據屬性的變化,通過呼叫this.setState()來更新你的元件狀態,舊的屬性還是可以通過this.props來獲取,這裏呼叫更新狀態是安全的,並不會觸發額外的render呼叫。

componentWillReceiveProps: function(nextProps){
    if(nextProps.checked !== undefined){
        this.setState({
            checked: nextProps.checked
        })
    }
}

shouldComponentUpdate

shouldComponentUpdate函式是重渲染時render()函式呼叫前被呼叫的函式,它接受兩個引數:nextProps和nextState,分別表示下一個props和下一個state的值。並且,當函式返回false時候,阻止接下來的render()函式及後面的 componentWillUpdate,componentDidUpdate 方法的呼叫,阻止元件重渲染,而返回true時,元件照常重渲染。

componentWillUpdate

這個方法和 componentWillMount 類似,在元件接收到了新的 props 或者 state 即將進行重新渲染前,componentWillUpdate(object nextProps, object nextState) 會被呼叫,注意不要在此方面裡再去更新 props 或者 state。

componentDidUpdate

這個方法和 componentDidMount 類似,在元件重新被渲染之後,componentDidUpdate(object prevProps, object prevState) 會被呼叫。可以在這裏訪問並修改 DOM。

銷燬

componentWillUnmount

每當React使用完一個元件,這個元件必須從 DOM 中解除安裝後被銷燬,此時 componentWillUnmout 會被執行,完成所有的清理和銷燬工作,在 componentDidMount 中新增的任務都需要再該方法中撤銷,如建立的定時器或事件監聽器。

當再次裝載元件時,以下方法會被依次呼叫:

1、getInitialState

2、componentWillMount

3、render

4、componentDidMount

React v.16生命週期

constructor(props) // 初始化引數

componentWillMount()

render() // 第一次渲染 

componentDidMount()

**當父元件向子元件傳入props發生改變後,依次呼叫**

componentWillReceiveProps(nextProps)

shouldComponentUpdate(nextProps, nextState) 

componentWillUpdate()

render() //子元件更新渲染

componentDidUpdate()

**當元件自身state發生變化後**

componentWillUpdate()

render() //元件再次更新渲染

componentDidUpdate()


當元件解除安裝

componentWillUnmount()

與低於React16版本的比較

  1. React16新的生命週期棄用了componentWillMount、componentWillReceiveProps,componentWillUpdate
  2. 新增了getDerivedStateFromProps、getSnapshotBeforeUpdate來代替棄用的三個鉤子函式(componentWillMount、componentWillReceivePorps,componentWillUpdate)
  3. React16並沒有刪除這三個鉤子函式,但是不能和新增的鉤子函式(getDerivedStateFromProps、getSnapshotBeforeUpdate)混用,React17將會刪除componentWillMount、componentWillReceivePorps,componentWillUpdate
  4. 新增了對錯誤的處理(componentDidCatch)
Facebook Profile photo
Written by Nat
This is the author box. A short description about the author of this article. Could be their website link, what they like to read about and so on. Profile