原文地址:medium.com/react-in-de…javascript
如何以及爲什麼從React組件到Fiber節點的一切內容html
React使用一個構建用戶界面的JavaScript庫,它的核心機制是跟蹤組件狀態的的變化,而後將更新的狀態投影在屏幕上。在React中,咱們把這個過程稱爲協調。咱們調用setState方法後,框架會檢測state和prop是否發生變化,並從新渲染UI組件。java
React文檔關於這個機制提供了很好的高層面概述: React元素的角色,生命週期方法,render
方法,以及應用於子組件的diff算法。由render
方法返回的不可變的React元素廣泛被認爲是React的「虛擬DOM」。那個術語早期幫助React解釋給人們,但它也形成了一些困惑,也再也不在React文檔中使用,這篇文章中,我會繼續稱之爲React元素的樹。react
除了React元素的樹,框架總還有用於保留狀態的內部實例(組件,DOM節點等)的一棵樹。從16版本開始,React推出了內部實例樹的新實現,以及管理它的算法(代碼上稱爲Fiber)。想要得知Fiber架構帶來的好處,能夠參見The how and why on React’s usage of linked list in Fiber。git
這篇文章花費了我不少時間,並且要是沒有 Dan Abramov! 👍的個幫助,也不會講解地如此全面。github
這是給你講解React內部架構系列的第一篇文章。這篇文章中,我想提供算法中重要概念和數據結構的深度概述。一旦咱們有足夠的背景,咱們就能夠探索這個算法以及用於遍歷和操做fiber樹的主要方法。系列中的下一篇文章將示範React如何使用這個算法來初始render以及操做state和props的更新,從那裏咱們將瞭解到調度(scheduler)的細節、子協調(child reconciliation)操做以及構建更新鏈表(effect list)。算法
這裏我將給你講述至關高級的內容,我保證你閱讀後能夠理解到併發(Concurrent)React內部工做背後的神奇。若是你想成爲React的貢獻者的話,這個系列的文章也能夠做爲你的嚮導。我一個逆向代碼的虔誠者(就是喜歡死磕源碼),因此這裏有不少關於React@16.6.0的資源連接。vim
這確實牽扯不少內容,因此若是你沒有立刻理解也沒必要有很大壓力,一切都值得花時間。須要注意的是你沒必要了解這些來使用React,這篇文章是關於React如何內部工做的。數據結構
這裏有個咱們在整個系列中都會使用到的簡單應用。咱們有個button,簡單的增長數字,而後渲染到屏幕上。多線程
這是實現:
class ClickCounter extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState((state) => {
return {count: state.count + 1};
});
}
render() {
return [
<button key="1" onClick={this.handleClick}>Update counter</button>,
<span key="2">{this.state.count}</span>
]
}
}
複製代碼
你能夠在這裏去執行它。正如你看到的,它是一個簡單組件,經過render
方法返回button
和span
兩個子組件。只要你點擊button,組件的狀態就會在處理器中更新,這繼而致使span元素中的text的更新。
React在**協調(reconciliation)**期間有執行不少活動,例如,React在第一次render時執行的操做,以及在咱們這個簡單的應用中狀態更新以後:
ClickCounter
的state
中的count
參數ClickCounter
的子組件以及他們的props
span
元素的props
在協調期間還執行其餘活動,像聲明週期方法或者更新refs。全部這些活動在Fiber架構中統一塊兒來被定義爲一個「工做(work)」。工做的類型一般取決於React元素(element)的類型,例如,對於一個類組件(class component),React須要建立實例,而對於方法組件(function component)則不須要這樣。正如你所知,React中有不少種元素,如類組件、方法組件、host組件(DOM節點)以及Portal等。元素的類型被定義在createElement方法中的第一個參數,這個方法一般用在render
方法中來場景一個元素。
在咱們探索這些執行的活動以及主要的Fiber算法時,咱們先來對React內部使用的數據結構有個認識。
React中每一個組件是一個UI表示,咱們能夠叫它視圖(view)或者模板(template),它由render
方法返回。這裏即是咱們ClickCounter
的模板:
<button key="1" onClick={this.onClick}>Update counter</button>
<span key="2">{this.state.count}</span>
複製代碼
一旦模板通過JSX編譯,最終得到一串React元素。這就是React組件的render
方法真實返回的東西,而不是HTML。由於咱們沒有要求使用JSX,因此ClickCounter
組件的render
方法也能夠寫成:
class ClickCounter {
...
render() {
return [
React.createElement(
'button',
{
key: '1',
onClick: this.onClick
},
'Update counter'
),
React.createElement(
'span',
{
key: '2'
},
this.state.count
)
]
}
}
複製代碼
render
方法中的React.createElement
調用能夠建立兩個數據結構:
[
{
$$typeof: Symbol(react.element),
type: 'button',
key: "1",
props: {
children: 'Update counter',
onClick: () => { ... }
}
},
{
$$typeof: Symbol(react.element),
type: 'span',
key: "2",
props: {
children: 0
}
}
]
複製代碼
你能夠看到React在這些對象上添加$$typeof
看成React元素的來惟一標示,且咱們還有type
、key
和props
來描述這個元素,這些值由你傳給了React.createElement
方法。這裏注意,React是如何把文本內容表達成span
和button
節點的孩子,click處理如何成爲button
元素props的一部分,這裏還有React元素上其餘一些字段如ref
已經超出了本文的範疇。
React元素ClickCounter
沒有任何props或者key:
{
$$typeof: Symbol(react.element),
key: null,
props: {},
ref: null,
type: ClickCounter
}
複製代碼
在**協調(reconciliation)**期間,由render
方法返回的每一個React元素都將合併到fiber節點的樹中,每一個React元素都有相對應的fiber節點,不像React元素,fiber不會在每次render時從新建立。這些可變的數據結構帶有組件的狀態以及DOM。
咱們以前討論的是框架根據React元素的類型來執行不一樣的活動,在咱們簡單的應用中,對於類組件ClickCounter
,它調用生命週期和render
方法,而span
host組件(DOM節點)則執行DOM變化,因此每一個React元素轉化成類型相對應的Fiber節點,這些類型描述了須要完成的工做。
當React元素首次被轉化成fiber節點時,React在createFiberFromTypeAndProps方法中使用這個元素中的數據建立fiber,在以後發生的更新中,React重用這個fiber節點,且經過相對應的React元素中數據更新必要的屬性。
React也可能須要基於key
屬性在層級中移動節點,或者若是對應的React元素再也不由render
方法返回時,則刪除掉它。
找出ChildReconciler方法,你能夠看到全部活動的列表,以及React在當前存在fiber節點上執行的對應方法。
由於React爲每一個React元素都建立了一個fiber,因此只要咱們有這些元素的一棵樹,那咱們就會有fiber節點的一棵樹。在咱們簡單應用案例中,它看起來以下:
全部fiber節點經過一個鏈表連接起來,這個鏈表使用了fiber節點中屬性:child
、sibling
和return
。關於如何和爲什麼這種方式,能夠查閱個人文章The how and why on React’s usage of linked list in Fiber。
在第一次渲染(render)以後,React最後獲得了一顆fiber樹,它反映了用於渲染UI的應用的狀態,這顆樹被看成current。當React開始處理更新時,它構建所謂的workInProgress
樹來反映未來刷新屏幕的狀態。
全部工做都在來自workInProgress
樹的fiber上執行。當React通過當前樹時,對於每個先存在的fiber節點,它都會建立一個替代(alternate)節點,這些節點組成了workInProgress
樹。這個節點是使用render
方法返回的React元素的數據建立的。一旦更新處理完以及全部相關工做完成,React就有一顆替代樹來準備刷新屏幕。一旦這顆workInProgress
樹渲染(render)在屏幕上,它便成了當前樹。
React的設計原則之一是連貫性。React老是一次性更新DOM,而不是隻顯示部分結果。這顆workInProgress
樹爲當作是‘草稿’,它對用戶是不可見的,以致於React能夠先處理全部組件,而後再刷新他們的改變到屏幕上。
在這個代碼中,能夠看到不少方法,這些方法持有來着current
和workInProgress
樹的fiber節點,這是一個這樣方法的簽名:
function updateHostComponent(current, workInProgress, renderExpirationTime) {...}
複製代碼
每一個fiber節點中的alternate字段持有它的一個副本,這個副本節點表示current
樹指向workInProgress
樹的,反之亦然,代碼以下:
function createWorkInProgress(current, ...) {
let workInProgress = current.alternate;
if (workInProgress === null) {
workInProgress = createFiber(...);
}
...
workInProgress.alternate = current;
current.alternate = workInProgress;
...
return workInProgress;
}
複製代碼
咱們能夠認爲React中組件是使用state和props的方法,用於計算UI展現。每一個其餘活動,像DOM變化或者調用生命週期方法,應當認爲是一個反作用,或者一個簡單的做用。做用(Effects)也在這個文檔中說起。
你之前可能作過請求數據,訂閱,或者在React組件中手動修改DOM,咱們把這些操做叫作反作用(或者簡說做用),由於它們會影響其餘組件,且不能在渲染時完成。
你能夠看到不少state和props是如何形成反作用的,
既然應用反作用是一個工做的類型,那一個fiber節點就是除了更新以外還用於跟蹤做用的簡明機制。每個fiber節點能夠有不少關聯的做用,它們被編碼到effectTag
字段中(effectTag使用位運算的妙處啦)。
因此Fiber中的做用(effects)基本上定義了一個組件實例在其更新操做以後須要完成的工做(work),對於host組件(DOM元素),這個工做包括更新、添加和刪除元素;對於類組件,React可能須要更新refs
,以及調用componentDidMount
和componentDidUpdate
生命週期方法。這裏固然還有其餘一些與fiber類型相對應的做用。
React處理更新很快,爲了實現這個層次的性能,它採用了個別有趣的技巧,好比,將含有做用的fiber節點用線性列表表示,從而能夠快速迭代。迭代線性列表比樹要快,且能夠沒必要花時間在沒有反作用的節點上。
這個列表的目的是用於標記一些節點,這些節點有DOM更新或者與其關聯的反作用。這個列表是finishedWork
的子集,且經過nextEffect
屬性連接起來,而不是current
和workInProgress
樹中使用的child
屬性。
Dan Abramov對做用列表做了一個類比,就像一顆聖誕樹中經過「聖誕燈」來把全部做用節點鏈接起來。爲了虛擬化它,咱們設想如下這顆fiber樹,其中點亮的節點有一些工做要作,例如,咱們更新使得c2
插入DOM中、d2
和c1
改變屬性,以及d2
觸發生命週期方法,那做用列表就講它們鏈接起來,以致於React以後能夠濾過其餘節點:
你能夠看到含有做用的節點和如何鏈接起來。當要遍歷這些節點時,React使用firstEffect
得出列表從哪裏開始,那上述的示意圖能夠用線性列表以下表示:
正如你所見,React執行做用的順序是從子向上到父的。
每一個React應用有一個或多個DOM元素做爲容器,在咱們的例子中,它是ID是container的div
元素:
const domContainer = document.querySelector('#container');
ReactDOM.render(React.createElement(ClickCounter), domContainer);
複製代碼
React爲這些容器的每一個建立一個fiber根節點,你能夠經過DOM元素的引用訪問它:
const fiberRoot = query('#container')._reactRootContainer._internalRoot
複製代碼
這個fiber根節點就是React持有fiber樹引用的地方,它保存在fiber根節點的current
屬性上:
const hostRootFiberNode = fiberRoot.current
複製代碼
fiber樹開始於HostRoot
的fiber節點的一個特殊類型,它由內部建立並將頂層組件做爲父節點。這裏有一個經過從stateNode
屬性從HostRoot
fiber節點返回到FiberRoot
的鏈接:
fiberRoot.current.stateNode === fiberRoot; // true
複製代碼
你能夠經過fiber根節點獲取HostFiber
節點來探索fiber樹,或者你能夠像這樣從組件實例中獲取獨立的fiber節點:
compInstance._reactInternalFiber
複製代碼
咱們來看一下由ClickCounter
組件建立的fiber節點的數據結構:
{
stateNode: new ClickCounter,
type: ClickCounter,
alternate: null,
key: null,
updateQueue: null,
memoizedState: {count: 0},
pendingProps: {},
memoizedProps: {},
tag: 1,
effectTag: 0,
nextEffect: null
}
複製代碼
以及span
DOM元素:
{
stateNode: new HTMLSpanElement,
type: "span",
alternate: null,
key: "2",
updateQueue: null,
memoizedState: null,
pendingProps: {children: 0},
memoizedProps: {children: 0},
tag: 5,
effectTag: 0,
nextEffect: null
}
複製代碼
fiber節點中有許多字段,我已經在前面有描述字段alternate
、effectTag
和nextEffect
的目的,如今我看看爲什麼還須要其餘字段。
持有類組件實例、DOM節點或者其餘與這個fiber節點關聯的React元素類型的引用,通常來講,咱們能夠說這個屬性用來持有與fiber相關的本地狀態。
定義與這個fiber關聯的方法或者類,對於類組件,它指向類的構造方法;對於DOM元素,它具體爲HTML標籤;我常常用這個字段來理解fiber節點關聯的是什麼元素。
定義了fiber的類型,這個用來在協調算法中定義那些工做須要完成。如以前所說,工做的不一樣取決於React元素類型,方法createFiberFromTypeAndProps映射了一個React元素到相對應fiber節點類型。在咱們的例子應用中,ClickCounter
組件的tag
屬性值爲1,表明了ClassComponent
,以及span
組件的是5,表明了HostComponent
。
一個包括狀態更新、callbacks以及DOM更新的隊列。
fiber中用於建立輸出的狀態,當處理更新時,它反映了當前已經渲染在屏幕上的狀態。
fiber中在前一次渲染時用於建立輸出的props。
由React元素中的新數據而來的已經更新過的props,且須要應用於子組件或者DOM元素。
一組子組件的惟一標示,用於React得出列表中哪一個改變了、添加了或者刪除了。這個與React中在這裏描述的的「列表與key」的功能相關。
你能夠從這裏獲得fiber節點的整個數據結構。我濾過了一些上面解釋過的字段。特別是我跳過了**child
、sibling
和return
,這些在我前一篇文章中介紹過了。還有一類字段像expirationTime
、childExpirationTime
以及mode
特定用於調度(Scheduler)**。
React執行工做主要有兩個階段:render 和 commit。
在第一個render
階段,React執行由setState
或者React.render
調度的組件上的更新,且得出在UI上哪些須要被更新,若是它是初始渲染,那React會爲每一個有render
方法返回的元素建立一個新的fiber節點,在後續的更新中,當前存在React元素的fiber會被重用和更新。這個階段的結果是一顆fiber節點被標記反作用的樹。這些做用被描述爲在接下來的commit
階段中須要完成的工做,在這個階段,React取標記做用的fiber樹並把它們應用到實例上,遍歷做用列表且執行DOM更新以及其餘用戶可見的變化。
理解在第一個render階段中執行的工做能夠是異步的很重要。React在可用的時間內能處理一個或多個fiber節點,而後中止來保存完成的工做並妥協於一些事件(好比優先級高的UI事件),它以後能夠在以前離開的方法在繼續執行,然而有時可能會丟棄已完成的工做,並從頂層重來。因爲這個階段的執行的工做不會致使用戶可見的變化(如DOM更新),因此這個暫停是可行的。不一樣的是,接下來的commit
階段老是同步的,由於這個階段的執行的工做會致使用戶可見的變化,這也是爲何React一把完成它們的緣由。
調用生命週期方法是React執行的一種工做類型,一些方法執行在render
階段,一些執行在commit
階段,下面是在render
階段中執行的生命週期方法列表:
正如你所見,一些在render階段
中被遺留的方法從16.3版本開始被標記爲UNSAFE
,它們在16.x的release版本中被棄用掉了,而它們不帶UNSAFE
前綴的副本在17.0中將被移除,你能夠在這裏閱讀更多關於這些改變,以及遷移建議的內容。
你好奇這個的緣由嗎?
那,咱們已經得知**render
階段不會形成反作用(如DOM更新),且React能夠對組件異步處理更新(且在的說,甚至能夠在多線程中執行)。而後被標記了UNSAFE
的聲明週期老是被誤解或者不易察覺的誤用,開發者傾向於把有反作用的邏輯放在這些方法中,這在新的異步渲染策略中可能會致使一些問題。儘管只有他們未標記UNSAFE
**前綴的副本被移除掉了,但它們仍然可能在將來的併發模型(Concurrent Mode)中形成問題,固然這個模式你能夠不啓用。
這裏是**commit
**階段執行的生命週期方法列表:
由於這些方法在同步的**commit
**階段執行,全部它們能夠包含反作用以及觸控DOM。
好,咱們如今已經有了必定的基礎去看看用於遍歷樹和執行工做的算法。
這個協調算法老是開始於頂層的HostRoot
fiber節點,這個節點由renderRoot方法建立,然而React可以跳過已經處理過的fiber節點,直到它找到還沒有完成工做的節點,例如,若是你在一個組件樹深處調用setState
,React將從頂部開始,可是很快就跳過一些節點,找到調用setState
方法的組件。
全部的fiber節點都會在work loop作處理,這裏是這個循環的同步部分的實現:
function workLoop(isYieldy) {
if (!isYieldy) {
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {...}
}
複製代碼
在上面代碼中,nextUnitOfWork
持有一個fiber節點,這個節點來自還有一些工做須要作的workInProgress
樹,正如React遍歷fiber樹同樣,它使用這個變量來知道是否還有其餘未完成工做的fiber節點,當前fiber節點處理以後,這個遍歷將要麼獲取下一個fiber節點的引用,要麼爲null
,在**null
**的狀況下,React將退出工做循環,並準備提交更新。
這裏有4個主要的方法,用於遍歷樹,以及初始化或者完成工做:
爲了示例它們是怎麼用的,看看下面遍歷fiber樹的動畫。我以及用demo把這些方法作了個簡單是的實現,每一個方法都會取fiber節點來處理,正如React沿着樹往下走時,你能夠看到當前活躍fiber節點的變化,這個視頻中你能夠清晰地看到算法是如何從一個樹枝走到另外一個樹枝的,它在移動到父節點前,首先得先完成子節點的工做。
注意:垂直直線鏈接表明兄弟,拐彎鏈接表明父子,如**
b1
沒有子節點,而b2
有一個c1
**孩子。
這是視頻鏈接,其中你能夠暫停播放,查看當前節點和方法的狀態。概念上,你能夠把「begin」看成進入組件,把「complete」看成離開組件,你也能夠在這裏執行這個例子和實現,正如我解釋這些方法所作的事情。
咱們從頭兩個方法**performUnitOfWork
和beginWork
**開始:
function performUnitOfWork(workInProgress) {
let next = beginWork(workInProgress);
if (next === null) {
next = completeUnitOfWork(workInProgress);
}
return next;
}
function beginWork(workInProgress) {
console.log('work performed for ' + workInProgress.name);
return workInProgress.child;
}
複製代碼
performUnitOfWork
方法從workInProgress
中接受一個fiber節點,調用beginWork
方法來開始工做。fiber節點上須要執行的全部活動都將從這個方法開始,對於這個示例的目的,咱們只打印一下組件名稱,就當是工做已經完成了。beginWork
老是返回一個指針,指向循環中要處理的下一個子節點,或者指向null
。
若是有下一個子節點,它會在**workLoop
方法中賦值給nextUnitOfWork
變量,而後,若是沒有子節點,React知道到達了樹枝的末尾,全部就能夠完成(complete)當前這個節點。一個節點只要完成了,它就會須要從兄弟和父級節點繼續執行工做,這在completeUnitOfWork
**方法中進行:
function completeUnitOfWork(workInProgress) {
while (true) {
let returnFiber = workInProgress.return;
let siblingFiber = workInProgress.sibling;
nextUnitOfWork = completeWork(workInProgress);
if (siblingFiber !== null) {
// If there is a sibling, return it
// to perform work for this sibling
return siblingFiber;
} else if (returnFiber !== null) {
// If there's no more work in this returnFiber,
// continue the loop to complete the parent.
workInProgress = returnFiber;
continue;
} else {
// We've reached the root.
return null;
}
}
}
function completeWork(workInProgress) {
console.log('work completed for ' + workInProgress.name);
return null;
}
複製代碼
你能夠從中看到方法大體是一個大的**while
循環,React當workInProgress
節點沒有孩子時就進入這個方法。在完成當前fiber的工做後,它檢查是否有還有兄弟,若是有,React退出這個方法,並返回指向兄弟的指針,它將會賦值給nextUnitOfWork
**變量,而後React將經過兄弟節點在新樹枝上開始執行工做。重要的是明白這種狀況中React只有是前面的兄弟節點完成了工做,而它尚未完成父節點的工做,只有全部開始於子節點的樹枝上的工做完成了,它纔算是爲父節點完成了工做,而後原路返回。
正如你從實現中所見,**performUnitOfWork
和completeUnitOfWork
方法的目的幾乎是迭代,而主要活動發生在beginWork
和completeWork
方法中。在這個系列接下來的文章中,咱們將知道,當React進入beginWork
和completeWork
方法中時,ClickCounter
組件和span
**節點會發生什麼。
這個階段開始於completeRoot方法,這裏React便會更新DOM,以及調用前先後置突變生命週期方法。
當React進入這個階段時,它有兩顆樹和一個做用列表,第一顆樹表示了當前渲染在屏幕上的狀態,而這裏還有在**render
階段構建的一顆替代樹,它調用代碼中finishedWork
和workInProgress
,表示須要在屏幕上反應出來的狀態,這顆替代樹連接方式相似當前樹,經過child
和sibling
**指針連接。
還有做用列表——經過**nextEffect
鏈接起來的finishedWork
樹的節點子集。記住做用列表是在render
**階段生成,整個渲染(rendering)的要點就是得出哪些節點須要插入、更新、刪除,以及哪些組件須要執行它們的生命週期方法,這即是做用列表要告訴咱們的,這是會在commit階段中被迭代的節點集合。
爲了debugging,當前樹能夠經過fiber根節點**
current
屬性方法,finishedWork
樹能夠經過當前樹上的HostFiber
節點的alternate
**來訪問。
主要運行在commit階段的方法是commitRoot,大體以下操做:
Snapshot
做用的節點執行getSnapshotBeforeUpdate
**生命週期方法。Deletion
做用的節點執行componentWillUnmount
**生命週期方法。finishedWork
**樹置爲當前樹。Placement
做用的節點執行componentDidMount
**生命週期方法。Update
做用的節點執行componentDidUpdate
**生命週期方法。調用前置突變方法**getSnapshotBeforeUpdate
以後,React提交了樹中全部反作用。它以兩個步驟來作,第一步是執行全部DOM(host)的插入、更新和刪除以及ref的卸載,而後React把finishedWork
樹賦值給FiberRoot
,即當workInProgress
樹爲current
樹,這在commit階段的第一步和第二步之間執行,便於以前的樹在componentWillUnmount
是仍是當前樹,而在componentDidMount/Update
**時,完成樹(finished work)爲當前樹。在第二步中,React調用其餘全部生命週期方法和ref回調,這些方法在單獨步驟中執行,以至整個樹中全部的替換、更新和刪除已經被調用。
這裏運行上述描述方法的大意:
function commitRoot(root, finishedWork) {
commitBeforeMutationLifecycles()
commitAllHostEffects();
root.current = finishedWork;
commitAllLifeCycles();
}
複製代碼
每一個子方法都實現了一個循環來迭代做用列表以及檢查做用類型,當發現和這個方法目的有關的做用,就應用它。
例如這裏的一個代碼,迭代做用樹,並檢查一個節點是不是**Snapshot
**做用:
function commitBeforeMutationLifecycles() {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
if (effectTag & Snapshot) {
const current = nextEffect.alternate;
commitBeforeMutationLifeCycles(current, nextEffect);
}
nextEffect = nextEffect.nextEffect;
}
}
複製代碼
對於類組件來講,這個做用意味着調用**getSnapshotBeforeUpdate
**方法。
commitAllHostEffects是React執行DOM更新的方法,這個方法定義了節點須要完成操做的類型,且執行它:
function commitAllHostEffects() {
switch (primaryEffectTag) {
case Placement: {
commitPlacement(nextEffect);
...
}
case PlacementAndUpdate: {
commitPlacement(nextEffect);
commitWork(current, nextEffect);
...
}
case Update: {
commitWork(current, nextEffect);
...
}
case Deletion: {
commitDeletion(nextEffect);
...
}
}
}
複製代碼
有趣的是,React在刪除操做中,把**commitDeletion
方法中調用componentWillUnmount
**方法看成其中一部分。
commitAllLifecycles是React調用全部剩餘生命週期方法**componentDidUpdate
和componentDidMount
**的方法。
這裏咱們就講完了。