日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

如何通俗易懂地解釋React生命周期方法?

 黃爸爸好 2019-02-02
作者|Matt Maribojoc
譯者|無明
什么是生命周期方法?新的 React16+ 生命周期方法是怎樣的?你該如何直觀地理解它們,以及為什么它們很有用?
生命周期方法到底是什么?

React 組件都有自己的階段。

如果要你“構(gòu)建一個 Hello World 組件”,我相信你會這么做:

class HelloWorld extends React.Component {   render() {    return <h1> Hello World </h1>   }}

在客戶端渲染這個組件時,你最終可能會看到如下的視圖:

在呈現(xiàn)這個視圖之前,這個組件經(jīng)歷了幾個階段。這些階段通常稱為組件生命周期。

對于人類而言,我們會經(jīng)歷小孩、成人、老人階段。而對于 React 組件而言,我們有掛載、更新和卸載階段。

巧合的是,掛載一個組件就像將一個新生嬰兒帶到這個世界。這是組件第一次擁有了生命。組件正是在這個階段被創(chuàng)建,然后被插入到 DOM 中。

這是組件經(jīng)歷的第一個階段——掛載階段。

但它并不會就這樣結(jié)束了。React 組件會“成長”,或者說組件會經(jīng)歷更新階段。

如果 React 組件不經(jīng)歷更新階段,它們將保持被創(chuàng)建時的狀態(tài)。

大部分組件會被更新——無論是通過修改 state 還是 props,也就是經(jīng)歷更新階段。

組件經(jīng)歷的最后一個階段是卸載階段。

在這個階段,組件會“死亡”。用 React 術(shù)語來描述,就是指從 DOM 中移除組件。

這些就是你需要了解的有關(guān)組件生命周期的一切。

對了,React 組件還需要經(jīng)歷另一個階段。有時候代碼會無法運(yùn)行或者某處出現(xiàn)了錯誤,這個時候組件正在經(jīng)歷錯誤處理階段,就像人類去看醫(yī)生。

現(xiàn)在,你了解了 React 組件的四個基本階段或者說生命周期。

  1. 掛載——組件在這個階段被創(chuàng)建然后被插入到 DOM 中;

  2. 更新——React 組件“成長”;

  3. 卸載——最后階段;

  4. 錯誤處理——有時候代碼無法運(yùn)行或某處出現(xiàn)了錯誤。

注意:React 組件可能不會經(jīng)歷所有階段。一個組件有可能在掛載后立即就被卸載——沒有更新或錯誤處理。

了解各個階段及其相關(guān)的生命周期方法

了解組件經(jīng)歷的各個階段只是整個等式的一部分,另一部分是了解每個階段所對應(yīng)的方法。

這些方法就是眾所周知的組件生命周期方法。

讓我們來看看這 4 個階段所對應(yīng)的方法。

我們先來看一下掛載階段的方法。

掛載生命周期方法

掛載階段是指從組件被創(chuàng)建到被插入 DOM 的階段。

這個階段會調(diào)用以下幾個方法(按順序描述)。

1. constructor()

這是給組件“帶來生命”時調(diào)用的第一個方法。

在將組件掛載到 DOM 之前會調(diào)用 constructor 方法。

通常,你會在 constructor 方法中初始化 state 和綁定事件處理程序。

這是一個簡單的例子:

const MyComponent extends React.Component {  constructor(props) {   super(props)     this.state = {       points: 0    }      this.handlePoints = this.handlePoints.bind(this)     }   }

我相信你已經(jīng)很熟悉這個方法了,所以我不打算進(jìn)一步再做解釋。

需要注意的是,這是第一個被調(diào)用的方法——在組件被掛載到 DOM 之前。

2. static getDerivedStateFromProps()

在解釋這個生命周期方法之前,我先說明如何使用這個方法。

這個方法的基本結(jié)構(gòu)如下所示:

const MyComponent extends React.Component {  ...  static getDerivedStateFromProps() {     //do stuff here  }  }

這個方法以 props 和 state 作為參數(shù):

...   static getDerivedStateFromProps(props, state) {    //do stuff here  }  ...

你可以返回一個用于更新組件狀態(tài)的對象:

...  static getDerivedStateFromProps(props, state) {     return {         points: 200 // update state with this     }  }    ...

或者返回 null,不進(jìn)行更新:

...   static getDerivedStateFromProps(props, state) {    return null  }  ...

你可能會想,這個生命周期方法很重要嗎?它是很少使用的生命周期方法之一,但它在某些情況下會派上用場。

請記住,這個方法在組件被初始掛載到 DOM 之前調(diào)用。

下面是一個簡單的例子:

假設(shè)有一個簡單的組件,用于呈現(xiàn)足球隊的得分。

得分被保存在組件的 state 對象中:

class App extends Component {  state = {    points: 10  }  render() {    return (      <div className='App'>        <header className='App-header'>          <img src={logo} className='App-logo' alt='logo' />          <p>            You've scored {this.state.points} points.          </p>        </header>      </div>    );  }}

結(jié)果如下所示:

源代碼可以在 GitHub 上獲得:https://github.com/ohansemmanuel/points

假設(shè)你像下面這樣在 static getDerivedStateFromProps 方法中放入其他分?jǐn)?shù),那么呈現(xiàn)的分?jǐn)?shù)是多少?

class App extends Component {  state = {    points: 10  }  // *******  //  NB: Not the recommended way to use this method. Just an example. Unconditionally overriding state here is generally considered a bad idea  // ********  static getDerivedStateFromProps(props, state) {    return {      points: 1000    }  }  render() {    return (      <div className='App'>        <header className='App-header'>          <img src={logo} className='App-logo' alt='logo' />          <p>            You've scored {this.state.points} points.          </p>        </header>      </div>    );  }}

現(xiàn)在我們有了 static getDerivedStateFromProps 組件生命周期方法。在將組件掛載到 DOM 之前這個方法會被調(diào)用。通過返回一個對象,我們可以在組件被渲染之前更新它的狀態(tài)。

我們將看到:

1000 來自 static getDerivedStateFromProps 方法的狀態(tài)更新。

當(dāng)然,這個例子主要是出于演示的目的,static getDerivedStateFromProps 方法不應(yīng)該被這么用。我這么做只是為了讓你先了解這些基礎(chǔ)知識。

我們可以使用這個生命周期方法來更新狀態(tài),但并不意味著必須這樣做。static getDerivedStateFromProps 方法有它特定的應(yīng)用場景。

那么什么時候應(yīng)該使用 static getDerivedStateFromProps 方法呢?

方法名 getDerivedStateFromProps 包含五個不同的單詞:“Get Fromived State From Props”。

顧名思義,這個方法允許組件基于 props 的變更來更新其內(nèi)部狀態(tài)。

此外,以這種方式獲得的組件狀態(tài)被稱為派生狀態(tài)。

根據(jù)經(jīng)驗,應(yīng)該謹(jǐn)慎使用派生狀態(tài),因為如果你不確定自己在做什么,很可能會向應(yīng)用程序引入潛在的錯誤。

3. render()

在調(diào)用 static getDerivedStateFromProps 方法之后,下一個生命周期方法是 render:

class MyComponent extends React.Component {    // render is the only required method for a class component   render() {    return <h1> Hurray! </h1>   }}

如果要渲染 DOM 中的元素,可以在 render 方法中編寫代碼,即返回一些 JSX。

你還可以返回純字符串和數(shù)字,如下所示:

class MyComponent extends React.Component {   render() {    return 'Hurray'    }}

或者返回數(shù)組和片段,如下所示:

class MyComponent extends React.Component {   render() {    return [          <div key='1'>Hello</div>,          <div key='2' >World</div>      ];   }}class MyComponent extends React.Component {   render() {    return <React.Fragment>            <div>Hello</div>            <div>World</div>      </React.Fragment>   }}

如果你不想渲染任何內(nèi)容,可以在 render 方法中返回一個布爾值或 null:

class MyComponent extends React.Component {    render() {    return null   }}class MyComponent extends React.Component {  // guess what's returned here?   render() {    return (2 + 2 === 5) && <div>Hello World</div>;  }}

你還可以從 render 方法返回一個 portal:

class MyComponent extends React.Component {  render() {    return createPortal(this.props.children, document.querySelector('body'));  }}

關(guān)于 render 方法的一個重要注意事項是,不要在函數(shù)中調(diào)用 setState 或者與外部 API 發(fā)生交互。

4. componentDidMount()

在調(diào)用 render 后,組件被掛載到 DOM,并調(diào)用 componentDidMount 方法。

在將組件被掛載到 DOM 之后會立即調(diào)用這個函數(shù)。

有時候你需要在組件掛載后立即從組件樹中獲取 DOM 節(jié)點,這個時候就可以調(diào)用這個組件生命周期方法。

例如,你可能有一個模態(tài)窗口,并希望在特定 DOM 元素中渲染模態(tài)窗口的內(nèi)容,你可以這么做:

class ModalContent extends React.Component {  el = document.createElement('section');  componentDidMount() {    document.querySelector('body).appendChild(this.el);  }  // using a portal, the content of the modal will be rendered in the DOM element attached to the DOM in the componentDidMount method. }

如果你希望在組件被掛載到 DOM 后立即發(fā)出網(wǎng)絡(luò)請求,可以在這個方法里進(jìn)行:

componentDidMount() {  this.fetchListOfTweets() // where fetchListOfTweets initiates a netowrk request to fetch a certain list of tweets. }

你還可以設(shè)置訂閱,例如計時器:

// e.g requestAnimationFrame componentDidMount() {    window.requestAnimationFrame(this._updateCountdown); }// e.g event listeners componentDidMount() {    el.addEventListener()}

只需要確保在卸載組件時取消訂閱,我們將在討論 componentWillUnmount 生命周期方法時介紹更詳細(xì)的內(nèi)容。

掛載階段基本上就是這樣了,現(xiàn)在讓我們來看看組件經(jīng)歷的下一個階段——更新階段。

更新生命周期方法

每當(dāng)更改 React 組件的 state 或 props 時,組件都會被重新渲染。簡單地說,就是組件被更新。這就是組件生命周期的更新階段。

那么在更新組件時會調(diào)用哪些生命周期方法?

1. static getDerivedStateFromProps()

首先,還會調(diào)用 static getDerivedStateFromProps 方法。這是第一個被調(diào)用的方法。因為之前已經(jīng)介紹過這個方法,所以這里不再解釋。

需要注意的是,在掛載和更新階段都會調(diào)用這個方法。

2. shouldComponentUpdate()

在調(diào)用 static getDerivedStateFromProps 方法之后,接下來會調(diào)用 nextComponentUpdate 方法。

默認(rèn)情況下,或者在大多數(shù)情況下,在 state 或 props 發(fā)生變更時會重新渲染組件。不過,你也可以控制這種行為。

你可以在這個方法中返回一個布爾值——true 或 false,用于控制是否重新渲染組件。

這個生命周期方法主要用于優(yōu)化性能。不過,如果 state 和 props 沒有發(fā)生變更,不希望組件重新渲染,你也可以使用內(nèi)置的 PureComponent。

3. render()

在調(diào)用 shouldComponentUpdate 方法后,會立即調(diào)用 render——具體取決于 shouldComponentUpdate 返回的值,默認(rèn)為 true。

4. getSnapshotBeforeUpdate()

在調(diào)用 render 方法之后,接下來會調(diào)用 getSnapshotBeforeUpdatelifcycle 方法。

你不一定會用到這個生命周期方法,但在某些特殊情況下它可能會派上用場,特別是當(dāng)你需要在 DOM 更新后從中獲取一些信息。

這里需要注意的是,getSnapshotBeforeUpdate 方法從 DOM 獲得的值將引用 DOM 更新之前的值,即使之前調(diào)用了 render 方法。

我們以使用 git 作為類比。

在編寫代碼時,你會在將代碼推送到代碼庫之前暫存它們。

假設(shè)在將變更推送到 DOM 之前調(diào)用了 render 函數(shù)來暫存變更。因此,在實際更新 DOM 之前,getSnapshotBeforeUpdate 獲得的信息指向了 DOM 更新之前的信息。

對 DOM 的更新可能是異步的,但 getSnapshotBeforeUpdate 生命周期方法在更新 DOM 之前立即被調(diào)用。

如果你還是不太明白,我再舉一個例子。

聊天應(yīng)用程序是這個生命周期方法的一個典型應(yīng)用場景。

我已經(jīng)為之前的示例應(yīng)用程序添加了聊天窗格。

可以看到右側(cè)的窗格嗎?

聊天窗格的實現(xiàn)非常簡單,你可能已經(jīng)想到了。在 App 組件中有一個帶有 Chats 組件的無序列表:

<ul className='chat-thread'>    <Chats chatList={this.state.chatList} /> </ul>

Chats 組件用于渲染聊天列表,為此,它需要一個 chatList prop。基本上它就是一個數(shù)組,一個包含 3 個字符串的數(shù)組:[“Hey”, “Hello”, “Hi”]。

Chats 組件的實現(xiàn)如下:

class Chats extends Component {  render() {    return (      <React.Fragment>        {this.props.chatList.map((chat, i) => (          <li key={i} className='chat-bubble'>            {chat}          </li>        ))}      </React.Fragment>    );  }}

它只是通過映射 chatList prop 并渲染出一個列表項,而該列表項的樣式看起來像氣泡。

還有一個東西,在聊天窗格頂部有一個“Add Chat”按鈕。

看到聊天窗格頂部的按鈕了嗎?

單擊這個按鈕將會添加新的聊天文本“Hello”,如下所示:

與大多數(shù)聊天應(yīng)用程序一樣,這里有一個問題:每當(dāng)消息數(shù)量超過聊天窗口的高度時,預(yù)期的行為應(yīng)該是自動向下滾動聊天窗格,以便看到最新的聊天消息。大現(xiàn)在的情況并非如此。

讓我們看看如何使用 getSnapshotBeforeUpdate 生命周期方法來解決這個問題。

在調(diào)用 getSnapshotBeforeUpdate 方法時,需要將之前的 props 和 state 作為參數(shù)傳給它。

我們可以使用 prevProps 和 prevState 參數(shù),如下所示:

getSnapshotBeforeUpdate(prevProps, prevState) {}

你可以讓這個方法返回一個值或 null:

getSnapshotBeforeUpdate(prevProps, prevState) {   return value || null // where 'value' is a  valid JavaScript value    }

無論這個方法返回什么值,都會被傳給另一個生命周期方法。

getSnapshotBeforeUpdate 生命周期方法本身不會起什么作用,它需要與 componentDidUpdate 生命周期方法結(jié)合在一起使用。

你先記住這個,讓我們來看一下 componentDidUpdate 生命周期方法。

5. componentDidUpdate()

在調(diào)用 getSnapshotBeforeUpdate 之后會調(diào)用這個生命周期方法。與 getSnapshotBeforeUpdate 方法一樣,它接收之前的 props 和 state 作為參數(shù):

componentDidUpdate(prevProps, prevState) {}

但這并不是全部。

無論從 getSnapshotBeforeUpdate 生命周期方法返回什么值,返回值都將被作為第三個參數(shù)傳給 componentDidUpdate 方法。

我們姑且把返回值叫作 snapshot,所以:

componentDidUpdate(prevProps, prevState, snapshot) {}

有了這些,接下來讓我們來解決聊天自動滾動位置的問題。

要解決這個問題,我需要提醒(或教導(dǎo))你一些 DOM 幾何學(xué)知識。

下面是保持聊天窗格滾動位置所需的代碼:

getSnapshotBeforeUpdate(prevProps, prevState) {    if (this.state.chatList > prevState.chatList) {      const chatThreadRef = this.chatThreadRef.current;      return chatThreadRef.scrollHeight - chatThreadRef.scrollTop;    }    return null;  }  componentDidUpdate(prevProps, prevState, snapshot) {    if (snapshot !== null) {      const chatThreadRef = this.chatThreadRef.current;      chatThreadRef.scrollTop = chatThreadRef.scrollHeight - snapshot;    }  }

這是聊天窗口:

下圖突出顯示了保存聊天消息的實際區(qū)域(無序列表 ul)。

我們在 ul 中添加了 React Ref:

<ul className='chat-thread' ref={this.chatThreadRef}>   ...</ul>

首先,因為 getSnapshotBeforeUpdate 可以通過任意數(shù)量的 props 或 state 更新來觸發(fā)更新,我們將通過一個條件來判斷是否有新的聊天消息:

getSnapshotBeforeUpdate(prevProps, prevState) {    if (this.state.chatList > prevState.chatList) {      // write logic here    }  }

getSnapshotBeforeUpdate 必須返回一個值。如果沒有添加新聊天消息,就返回 null:

getSnapshotBeforeUpdate(prevProps, prevState) {    if (this.state.chatList > prevState.chatList) {      // write logic here    }      return null }

現(xiàn)在看一下 getSnapshotBeforeUpdate 方法的完整代碼:

getSnapshotBeforeUpdate(prevProps, prevState) {    if (this.state.chatList > prevState.chatList) {      const chatThreadRef = this.chatThreadRef.current;      return chatThreadRef.scrollHeight - chatThreadRef.scrollTop;    }    return null;  }

我們先考慮一種情況,即所有聊天消息的高度不超過聊天窗格的高度。

表達(dá)式 chatThreadRef.scrollHeight - chatThreadRef.scrollTop 等同于 chatThreadRef.scrollHeight - 0。

這個表達(dá)式的值將等于聊天窗格的 scrollHeight——在將新消息插入 DOM 之前的高度。

之前我們已經(jīng)解釋過,從 getSnapshotBeforeUpdate 方法返回的值將作為第三個參數(shù)傳給 componentDidUpdate 方法,也就是 snapshot:

componentDidUpdate(prevProps, prevState, snapshot) { }

這個值是更新 DOM 之前的 scrollHeight。

componentDidUpdate 方法有以下這些代碼,但它們有什么作用呢?

componentDidUpdate(prevProps, prevState, snapshot) {    if (snapshot !== null) {      const chatThreadRef = this.chatThreadRef.current;      chatThreadRef.scrollTop = chatThreadRef.scrollHeight - snapshot;    }  }

實際上,我們以編程方式從上到下垂直滾動窗格,距離等于 chatThreadRef.scrollHeight - snapshot;。

由于 snapshot 是指更新前的 scrollHeight,上述的表達(dá)式將返回新聊天消息的高度,以及由于更新而導(dǎo)致的任何其他相關(guān)高度。請看下圖:

當(dāng)整個聊天窗格高度被消息占滿(并且已經(jīng)向上滾動一點)時,getSnapshotBeforeUpdate 方法返回的 snapshot 值將等于聊天窗格的實際高度。

componentDidUpdate 將 scrollTop 值設(shè)置為額外消息高度的總和,這正是我們想要的。

卸載生命周期方法

在組件卸載階段會調(diào)用下面這個方法。

componentWillUnmount()

在卸載和銷毀組件之前會調(diào)用 componentWillUnmount 生命周期方法。這是進(jìn)行資源清理最理想的地方,例如清除計時器、取消網(wǎng)絡(luò)請求或清理在 componentDidMount() 中創(chuàng)建的任何訂閱,如下所示:

// e.g add event listenercomponentDidMount() {    el.addEventListener()}// e.g remove event listener componentWillUnmount() {    el.removeEventListener() }
錯誤處理生命周期方法

有時候組件會出現(xiàn)問題,會拋出錯誤。當(dāng)后代組件(即組件下面的組件)拋出錯誤時,將調(diào)用下面的方法。

讓我們實現(xiàn)一個簡單的組件來捕獲演示應(yīng)用程序中的錯誤。為此,我們將創(chuàng)建一個叫作 ErrorBoundary 的新組件。

這是最基本的實現(xiàn):

import React, { Component } from 'react';class ErrorBoundary extends Component {  state = {};  render() {    return null;  }}export default ErrorBoundary;
static getDerivedStateFromError()

當(dāng)后代組件拋出錯誤時,首先會調(diào)用這個方法,并將拋出的錯誤作為參數(shù)。

無論這個方法返回什么值,都將用于更新組件的狀態(tài)。

讓 ErrorBoundary 組件使用這個生命周期方法:

import React, { Component } from 'react';class ErrorBoundary extends Component {  state = {};  static getDerivedStateFromError(error) {    console.log(`Error log from getDerivedStateFromError: ${error}`);    return { hasError: true };  }  render() {    return null;  }}export default ErrorBoundary;

現(xiàn)在,只要后代組件拋出錯誤,錯誤就會被記錄到控制臺,并且 getDerivedStateFromError 方法會返回一個對象,這個對象將用于更新 ErrorBoundary 組件的狀態(tài)。

componentDidCatch()

在后代組件拋出錯誤之后,也會調(diào)用 componentDidCatch 方法。除了拋出的錯誤之外,還會有另一個參數(shù),這個參數(shù)包含了有關(guān)錯誤的更多信息:

componentDidCatch(error, info) {}

在這個方法中,你可以將收到的 error 或 info 發(fā)送到外部日志記錄服務(wù)。與 getDerivedStateFromError 不同,componentDidCatch 允許包含會產(chǎn)生副作用的代碼:

componentDidCatch(error, info) {    logToExternalService(error, info) // this is allowed.         //Where logToExternalService may make an API call.}

讓 ErrorBoundary 組件使用這個生命周期方法:

import React, { Component } from 'react';class ErrorBoundary extends Component {  state = { hasError: false };  static getDerivedStateFromError(error) {    console.log(`Error log from getDerivedStateFromError: ${error}`);    return { hasError: true };  }  componentDidCatch(error, info) {    console.log(`Error log from componentDidCatch: ${error}`);    console.log(info);  }  render() {    return null  }}export default ErrorBoundary;

此外,由于 ErrorBoundary 只能捕捉后代組件拋出的錯誤,因此我們將讓組件渲染傳進(jìn)來的 Children,或者在出現(xiàn)錯誤時呈現(xiàn)默認(rèn)的錯誤 UI:

... render() {    if (this.state.hasError) {      return <h1>Something went wrong.</h1>;    }    return this.props.children; }

英文原文:https://blog./the-new-react-lifecycle-methods-in-plain-approachable-language-61a2105859f3

 活動推薦

移動端上軟硬件在不斷升級,移動端上越來越廣泛的使用算法,比如人臉識別、背景分割等。面對這類新趨勢,ArchSummit 全球架構(gòu)師峰會將邀請正在實施的團(tuán)隊技術(shù)人來介紹最新成果。

大會 7 折報名中,歡迎咨詢票務(wù)經(jīng)理 Lachel- 灰灰,電話 / 微信:17326843116

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多