|
React 組件都有自己的階段。 如果要你“構(gòu)建一個(gè) Hello World 組件”,我相信你會(huì)這么做: class HelloWorld extends React.Component { render() { return <h1> Hello World </h1> }}在客戶端渲染這個(gè)組件時(shí),你最終可能會(huì)看到如下的視圖: 在呈現(xiàn)這個(gè)視圖之前,這個(gè)組件經(jīng)歷了幾個(gè)階段。這些階段通常稱為組件生命周期。 對(duì)于人類而言,我們會(huì)經(jīng)歷小孩、成人、老人階段。而對(duì)于 React 組件而言,我們有掛載、更新和卸載階段。 巧合的是,掛載一個(gè)組件就像將一個(gè)新生嬰兒帶到這個(gè)世界。這是組件第一次擁有了生命。組件正是在這個(gè)階段被創(chuàng)建,然后被插入到 DOM 中。 這是組件經(jīng)歷的第一個(gè)階段——掛載階段。 但它并不會(huì)就這樣結(jié)束了。React 組件會(huì)“成長(zhǎng)”,或者說(shuō)組件會(huì)經(jīng)歷更新階段。 如果 React 組件不經(jīng)歷更新階段,它們將保持被創(chuàng)建時(shí)的狀態(tài)。 大部分組件會(huì)被更新——無(wú)論是通過(guò)修改 state 還是 props,也就是經(jīng)歷更新階段。 組件經(jīng)歷的最后一個(gè)階段是卸載階段。 在這個(gè)階段,組件會(huì)“死亡”。用 React 術(shù)語(yǔ)來(lái)描述,就是指從 DOM 中移除組件。 這些就是你需要了解的有關(guān)組件生命周期的一切。 對(duì)了,React 組件還需要經(jīng)歷另一個(gè)階段。有時(shí)候代碼會(huì)無(wú)法運(yùn)行或者某處出現(xiàn)了錯(cuò)誤,這個(gè)時(shí)候組件正在經(jīng)歷錯(cuò)誤處理階段,就像人類去看醫(yī)生。 現(xiàn)在,你了解了 React 組件的四個(gè)基本階段或者說(shuō)生命周期。
注意:React 組件可能不會(huì)經(jīng)歷所有階段。一個(gè)組件有可能在掛載后立即就被卸載——沒(méi)有更新或錯(cuò)誤處理。 了解組件經(jīng)歷的各個(gè)階段只是整個(gè)等式的一部分,另一部分是了解每個(gè)階段所對(duì)應(yīng)的方法。 這些方法就是眾所周知的組件生命周期方法。 讓我們來(lái)看看這 4 個(gè)階段所對(duì)應(yīng)的方法。 我們先來(lái)看一下掛載階段的方法。 掛載階段是指從組件被創(chuàng)建到被插入 DOM 的階段。 這個(gè)階段會(huì)調(diào)用以下幾個(gè)方法(按順序描述)。 這是給組件“帶來(lái)生命”時(shí)調(diào)用的第一個(gè)方法。 在將組件掛載到 DOM 之前會(huì)調(diào)用 constructor 方法。 通常,你會(huì)在 constructor 方法中初始化 state 和綁定事件處理程序。 這是一個(gè)簡(jiǎn)單的例子: 我相信你已經(jīng)很熟悉這個(gè)方法了,所以我不打算進(jìn)一步再做解釋。 需要注意的是,這是第一個(gè)被調(diào)用的方法——在組件被掛載到 DOM 之前。 在解釋這個(gè)生命周期方法之前,我先說(shuō)明如何使用這個(gè)方法。 這個(gè)方法的基本結(jié)構(gòu)如下所示: const MyComponent extends React.Component { ... static getDerivedStateFromProps() { //do stuff here } }這個(gè)方法以 props 和 state 作為參數(shù): 你可以返回一個(gè)用于更新組件狀態(tài)的對(duì)象: ... static getDerivedStateFromProps(props, state) { return { points: 200 // update state with this } } ...或者返回 null,不進(jìn)行更新: 你可能會(huì)想,這個(gè)生命周期方法很重要嗎?它是很少使用的生命周期方法之一,但它在某些情況下會(huì)派上用場(chǎng)。 請(qǐng)記住,這個(gè)方法在組件被初始掛載到 DOM 之前調(diào)用。 下面是一個(gè)簡(jiǎn)單的例子: 假設(shè)有一個(gè)簡(jiǎn)單的組件,用于呈現(xiàn)足球隊(duì)的得分。 得分被保存在組件的 state 對(duì)象中: 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ù)是多少? 現(xiàn)在我們有了 static getDerivedStateFromProps 組件生命周期方法。在將組件掛載到 DOM 之前這個(gè)方法會(huì)被調(diào)用。通過(guò)返回一個(gè)對(duì)象,我們可以在組件被渲染之前更新它的狀態(tài)。 我們將看到: 1000 來(lái)自 static getDerivedStateFromProps 方法的狀態(tài)更新。 當(dāng)然,這個(gè)例子主要是出于演示的目的,static getDerivedStateFromProps 方法不應(yīng)該被這么用。我這么做只是為了讓你先了解這些基礎(chǔ)知識(shí)。 我們可以使用這個(gè)生命周期方法來(lái)更新?tīng)顟B(tài),但并不意味著必須這樣做。static getDerivedStateFromProps 方法有它特定的應(yīng)用場(chǎng)景。 那么什么時(shí)候應(yīng)該使用 static getDerivedStateFromProps 方法呢? 方法名 getDerivedStateFromProps 包含五個(gè)不同的單詞:“Get Fromived State From Props”。 顧名思義,這個(gè)方法允許組件基于 props 的變更來(lái)更新其內(nèi)部狀態(tài)。 此外,以這種方式獲得的組件狀態(tài)被稱為派生狀態(tài)。 根據(jù)經(jīng)驗(yàn),應(yīng)該謹(jǐn)慎使用派生狀態(tài),因?yàn)槿绻悴淮_定自己在做什么,很可能會(huì)向應(yīng)用程序引入潛在的錯(cuò)誤。 在調(diào)用 static getDerivedStateFromProps 方法之后,下一個(gè)生命周期方法是 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ù)字,如下所示: 或者返回?cái)?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 方法中返回一個(gè)布爾值或 null: 你還可以從 render 方法返回一個(gè) portal: class MyComponent extends React.Component { render() { return createPortal(this.props.children, document.querySelector('body')); }}關(guān)于 render 方法的一個(gè)重要注意事項(xiàng)是,不要在函數(shù)中調(diào)用 setState 或者與外部 API 發(fā)生交互。 在調(diào)用 render 后,組件被掛載到 DOM,并調(diào)用 componentDidMount 方法。 在將組件被掛載到 DOM 之后會(huì)立即調(diào)用這個(gè)函數(shù)。 有時(shí)候你需要在組件掛載后立即從組件樹(shù)中獲取 DOM 節(jié)點(diǎn),這個(gè)時(shí)候就可以調(diào)用這個(gè)組件生命周期方法。 例如,你可能有一個(gè)模態(tài)窗口,并希望在特定 DOM 元素中渲染模態(tài)窗口的內(nèi)容,你可以這么做: 如果你希望在組件被掛載到 DOM 后立即發(fā)出網(wǎng)絡(luò)請(qǐng)求,可以在這個(gè)方法里進(jìn)行: componentDidMount() { this.fetchListOfTweets() // where fetchListOfTweets initiates a netowrk request to fetch a certain list of tweets. }你還可以設(shè)置訂閱,例如計(jì)時(shí)器: 只需要確保在卸載組件時(shí)取消訂閱,我們將在討論 componentWillUnmount 生命周期方法時(shí)介紹更詳細(xì)的內(nèi)容。 掛載階段基本上就是這樣了,現(xiàn)在讓我們來(lái)看看組件經(jīng)歷的下一個(gè)階段——更新階段。 每當(dāng)更改 React 組件的 state 或 props 時(shí),組件都會(huì)被重新渲染。簡(jiǎn)單地說(shuō),就是組件被更新。這就是組件生命周期的更新階段。 那么在更新組件時(shí)會(huì)調(diào)用哪些生命周期方法? 首先,還會(huì)調(diào)用 static getDerivedStateFromProps 方法。這是第一個(gè)被調(diào)用的方法。因?yàn)橹耙呀?jīng)介紹過(guò)這個(gè)方法,所以這里不再解釋。 需要注意的是,在掛載和更新階段都會(huì)調(diào)用這個(gè)方法。 在調(diào)用 static getDerivedStateFromProps 方法之后,接下來(lái)會(huì)調(diào)用 nextComponentUpdate 方法。 默認(rèn)情況下,或者在大多數(shù)情況下,在 state 或 props 發(fā)生變更時(shí)會(huì)重新渲染組件。不過(guò),你也可以控制這種行為。 你可以在這個(gè)方法中返回一個(gè)布爾值——true 或 false,用于控制是否重新渲染組件。 這個(gè)生命周期方法主要用于優(yōu)化性能。不過(guò),如果 state 和 props 沒(méi)有發(fā)生變更,不希望組件重新渲染,你也可以使用內(nèi)置的 PureComponent。 在調(diào)用 shouldComponentUpdate 方法后,會(huì)立即調(diào)用 render——具體取決于 shouldComponentUpdate 返回的值,默認(rèn)為 true。 在調(diào)用 render 方法之后,接下來(lái)會(huì)調(diào)用 getSnapshotBeforeUpdatelifcycle 方法。 你不一定會(huì)用到這個(gè)生命周期方法,但在某些特殊情況下它可能會(huì)派上用場(chǎng),特別是當(dāng)你需要在 DOM 更新后從中獲取一些信息。 這里需要注意的是,getSnapshotBeforeUpdate 方法從 DOM 獲得的值將引用 DOM 更新之前的值,即使之前調(diào)用了 render 方法。 我們以使用 git 作為類比。 在編寫代碼時(shí),你會(huì)在將代碼推送到代碼庫(kù)之前暫存它們。 假設(shè)在將變更推送到 DOM 之前調(diào)用了 render 函數(shù)來(lái)暫存變更。因此,在實(shí)際更新 DOM 之前,getSnapshotBeforeUpdate 獲得的信息指向了 DOM 更新之前的信息。 對(duì) DOM 的更新可能是異步的,但 getSnapshotBeforeUpdate 生命周期方法在更新 DOM 之前立即被調(diào)用。 如果你還是不太明白,我再舉一個(gè)例子。 聊天應(yīng)用程序是這個(gè)生命周期方法的一個(gè)典型應(yīng)用場(chǎng)景。 我已經(jīng)為之前的示例應(yīng)用程序添加了聊天窗格。 可以看到右側(cè)的窗格嗎? 聊天窗格的實(shí)現(xiàn)非常簡(jiǎn)單,你可能已經(jīng)想到了。在 App 組件中有一個(gè)帶有 Chats 組件的無(wú)序列表: <ul className='chat-thread'> <Chats chatList={this.state.chatList} /> </ul>Chats 組件用于渲染聊天列表,為此,它需要一個(gè) chatList prop。基本上它就是一個(gè)數(shù)組,一個(gè)包含 3 個(gè)字符串的數(shù)組:[“Hey”, “Hello”, “Hi”]。 Chats 組件的實(shí)現(xiàn)如下: 它只是通過(guò)映射 chatList prop 并渲染出一個(gè)列表項(xiàng),而該列表項(xiàng)的樣式看起來(lái)像氣泡。 還有一個(gè)東西,在聊天窗格頂部有一個(gè)“Add Chat”按鈕。 看到聊天窗格頂部的按鈕了嗎? 單擊這個(gè)按鈕將會(huì)添加新的聊天文本“Hello”,如下所示:
與大多數(shù)聊天應(yīng)用程序一樣,這里有一個(gè)問(wèn)題:每當(dāng)消息數(shù)量超過(guò)聊天窗口的高度時(shí),預(yù)期的行為應(yīng)該是自動(dòng)向下滾動(dòng)聊天窗格,以便看到最新的聊天消息。大現(xiàn)在的情況并非如此。
讓我們看看如何使用 getSnapshotBeforeUpdate 生命周期方法來(lái)解決這個(gè)問(wèn)題。 在調(diào)用 getSnapshotBeforeUpdate 方法時(shí),需要將之前的 props 和 state 作為參數(shù)傳給它。 我們可以使用 prevProps 和 prevState 參數(shù),如下所示: getSnapshotBeforeUpdate(prevProps, prevState) {}你可以讓這個(gè)方法返回一個(gè)值或 null: 無(wú)論這個(gè)方法返回什么值,都會(huì)被傳給另一個(gè)生命周期方法。 getSnapshotBeforeUpdate 生命周期方法本身不會(huì)起什么作用,它需要與 componentDidUpdate 生命周期方法結(jié)合在一起使用。 你先記住這個(gè),讓我們來(lái)看一下 componentDidUpdate 生命周期方法。 在調(diào)用 getSnapshotBeforeUpdate 之后會(huì)調(diào)用這個(gè)生命周期方法。與 getSnapshotBeforeUpdate 方法一樣,它接收之前的 props 和 state 作為參數(shù): componentDidUpdate(prevProps, prevState) {}但這并不是全部。 無(wú)論從 getSnapshotBeforeUpdate 生命周期方法返回什么值,返回值都將被作為第三個(gè)參數(shù)傳給 componentDidUpdate 方法。 我們姑且把返回值叫作 snapshot,所以: 有了這些,接下來(lái)讓我們來(lái)解決聊天自動(dòng)滾動(dòng)位置的問(wèn)題。 要解決這個(gè)問(wèn)題,我需要提醒(或教導(dǎo))你一些 DOM 幾何學(xué)知識(shí)。 下面是保持聊天窗格滾動(dòng)位置所需的代碼: 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; } }這是聊天窗口:
下圖突出顯示了保存聊天消息的實(shí)際區(qū)域(無(wú)序列表 ul)。
我們?cè)?ul 中添加了 React Ref: 首先,因?yàn)?getSnapshotBeforeUpdate 可以通過(guò)任意數(shù)量的 props 或 state 更新來(lái)觸發(fā)更新,我們將通過(guò)一個(gè)條件來(lái)判斷是否有新的聊天消息: getSnapshotBeforeUpdate(prevProps, prevState) { if (this.state.chatList > prevState.chatList) { // write logic here } }getSnapshotBeforeUpdate 必須返回一個(gè)值。如果沒(méi)有添加新聊天消息,就返回 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; }我們先考慮一種情況,即所有聊天消息的高度不超過(guò)聊天窗格的高度。
表達(dá)式 chatThreadRef.scrollHeight - chatThreadRef.scrollTop 等同于 chatThreadRef.scrollHeight - 0。 這個(gè)表達(dá)式的值將等于聊天窗格的 scrollHeight——在將新消息插入 DOM 之前的高度。 之前我們已經(jīng)解釋過(guò),從 getSnapshotBeforeUpdate 方法返回的值將作為第三個(gè)參數(shù)傳給 componentDidUpdate 方法,也就是 snapshot: 這個(gè)值是更新 DOM 之前的 scrollHeight。 componentDidUpdate 方法有以下這些代碼,但它們有什么作用呢? componentDidUpdate(prevProps, prevState, snapshot) { if (snapshot !== null) { const chatThreadRef = this.chatThreadRef.current; chatThreadRef.scrollTop = chatThreadRef.scrollHeight - snapshot; } }實(shí)際上,我們以編程方式從上到下垂直滾動(dòng)窗格,距離等于 chatThreadRef.scrollHeight - snapshot;。 由于 snapshot 是指更新前的 scrollHeight,上述的表達(dá)式將返回新聊天消息的高度,以及由于更新而導(dǎo)致的任何其他相關(guān)高度。請(qǐng)看下圖:
當(dāng)整個(gè)聊天窗格高度被消息占滿(并且已經(jīng)向上滾動(dòng)一點(diǎn))時(shí),getSnapshotBeforeUpdate 方法返回的 snapshot 值將等于聊天窗格的實(shí)際高度。
componentDidUpdate 將 scrollTop 值設(shè)置為額外消息高度的總和,這正是我們想要的。
在組件卸載階段會(huì)調(diào)用下面這個(gè)方法。 在卸載和銷毀組件之前會(huì)調(diào)用 componentWillUnmount 生命周期方法。這是進(jìn)行資源清理最理想的地方,例如清除計(jì)時(shí)器、取消網(wǎng)絡(luò)請(qǐng)求或清理在 componentDidMount() 中創(chuàng)建的任何訂閱,如下所示: 有時(shí)候組件會(huì)出現(xiàn)問(wèn)題,會(huì)拋出錯(cuò)誤。當(dāng)后代組件(即組件下面的組件)拋出錯(cuò)誤時(shí),將調(diào)用下面的方法。 讓我們實(shí)現(xiàn)一個(gè)簡(jiǎn)單的組件來(lái)捕獲演示應(yīng)用程序中的錯(cuò)誤。為此,我們將創(chuàng)建一個(gè)叫作 ErrorBoundary 的新組件。 這是最基本的實(shí)現(xiàn): import React, { Component } from 'react';class ErrorBoundary extends Component { state = {}; render() { return null; }}export default ErrorBoundary;當(dāng)后代組件拋出錯(cuò)誤時(shí),首先會(huì)調(diào)用這個(gè)方法,并將拋出的錯(cuò)誤作為參數(shù)。 無(wú)論這個(gè)方法返回什么值,都將用于更新組件的狀態(tài)。 讓 ErrorBoundary 組件使用這個(gè)生命周期方法: 現(xiàn)在,只要后代組件拋出錯(cuò)誤,錯(cuò)誤就會(huì)被記錄到控制臺(tái),并且 getDerivedStateFromError 方法會(huì)返回一個(gè)對(duì)象,這個(gè)對(duì)象將用于更新 ErrorBoundary 組件的狀態(tài)。 在后代組件拋出錯(cuò)誤之后,也會(huì)調(diào)用 componentDidCatch 方法。除了拋出的錯(cuò)誤之外,還會(huì)有另一個(gè)參數(shù),這個(gè)參數(shù)包含了有關(guān)錯(cuò)誤的更多信息: componentDidCatch(error, info) {}在這個(gè)方法中,你可以將收到的 error 或 info 發(fā)送到外部日志記錄服務(wù)。與 getDerivedStateFromError 不同,componentDidCatch 允許包含會(huì)產(chǎn)生副作用的代碼: 讓 ErrorBoundary 組件使用這個(gè)生命周期方法: 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 只能捕捉后代組件拋出的錯(cuò)誤,因此我們將讓組件渲染傳進(jìn)來(lái)的 Children,或者在出現(xiàn)錯(cuò)誤時(shí)呈現(xiàn)默認(rèn)的錯(cuò)誤 UI: 英文原文:https://blog./the-new-react-lifecycle-methods-in-plain-approachable-language-61a2105859f3 移動(dòng)端上軟硬件在不斷升級(jí),移動(dòng)端上越來(lái)越廣泛的使用算法,比如人臉識(shí)別、背景分割等。面對(duì)這類新趨勢(shì),ArchSummit 全球架構(gòu)師峰會(huì)將邀請(qǐng)正在實(shí)施的團(tuán)隊(duì)技術(shù)人來(lái)介紹最新成果。 大會(huì) 7 折報(bào)名中,歡迎咨詢票務(wù)經(jīng)理 Lachel- 灰灰,電話 / 微信:17326843116
|
|
|
來(lái)自: 黃爸爸好 > 《reactive》