其實,我也不懂,關於源碼,我看了兩次,只看懂了40%,丟臉了,可是仍是硬着頭皮,寫篇文章,mark一下吧,源碼來自 《深刻React技術棧》這本書....我只是大天然的搬運工javascript
學習一個框架,最重要的莫過於對生命週期的理解了。嗯,很懵,可是人傻就是要多看書,多看掘金上的優秀文章,看了兩篇React生命週期的文章以後,大概也能懂得個大概。就記錄一下吧 ~java
[注意
] 這是 react16.3以前
的生命週期node
《深刻React技術棧》中對生命週期的說明:react
渲染的過程:git
上圖中的getDefaultProps和getInitialState分別對應ES6中的static defaultProps = {}與構造函數construct中的this.state ={}賦值github
一個初始化組件 (以ES6 classes爲例子)
// 當使用 ES6 classes 編寫 React 組件時,其實就是調用內部方法 createClass 建立組件
import React, { Component } from 'react'
class Index extends Component {
static propTypes = {
// code...
}
static defaultProps = {
// code...
}
constructor(props) {
super(props)
this.state = {
// code...
}
}
componentWillMount () {
// code...
}
componentDidMount () {
}
render () {
return (
// code...
)
}
}
複製代碼
咱們來看看《深刻React技術》中如何解讀源碼瀏覽器
var React = {
// ...
createClass: ReactClass.createClass,
// ...
}
var ReactClass = {
createClass: function(spec) {
var Constructor = function(props, context, updater) {
// 自動綁定
if (this.__reactAutoBindPairs.length) {
bindAutoBindMethods(this);
}
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
this.state = null;
// ReactClass 沒有構造函數,經過 getInitialState 和 componentWillMount 來代替
var initialState = this.getInitialState ? this.getInitialState() : null;
this.state = initialState;
}
// 原型繼承父類
Constructor.prototype = new ReactClassComponent();
Constructor.prototype.constructor = Constructor;
Constructor.prototype.__reactAutoBindPairs = [];
// 合併 mixin
injectedMixins.forEach(
mixSpecIntoComponent.bind(null, Constructor)
);
mixSpecIntoComponent(Constructor, spec);
// 全部 mixin 合併後初始化 defaultProps(在整個生命週期中,getDefaultProps 只執行一次)
if (Constructor.getDefaultProps) {
Constructor.defaultProps = Constructor.getDefaultProps();
}
// 減小查找並設置原型的時間
for (var methodName in ReactClassInterface) {
if (!Constructor.prototype[methodName]) {
Constructor.prototype[methodName] = null;
}
}
return Constructor;
}
}
// React Constructor 說明
React規定constructor有三個參數,分別是props、context和updater。
· props是屬性,它是不可變的。
· context是全局上下文。
· updater是包含一些更新方法的對象
// this.setState最終調用的是this.updater.enqueueSetState方法
// this.forceUpdate最終調用的是this.updater.enqueueForceUpdate方法
複製代碼
mountComponent 組件掛載代碼app
// 當組件掛載時,會分配一個遞增編號,表示執行 ReactUpdates 時更新組件的順序
var nextMountID = 1
// 初始化組件,渲染標記,註冊事件監聽器
mountComponent: function(transaction, nativeParent, nativeContainerInfo, context) {
// 當前元素對應的上下文
this._context = context
this._mountOrder = nextMountID
this._nativeParent = nativeParent
this._nativeContainerInfo = nativeContainerInfo
var publicProps = this._processProps(this._currentElement.props)
var publicContext = this._processContext(context)
var Component = this._currentElement.type
// 初始化公共類
var inst = this._constructComponent(publicProps, publicContext)
var renderedElement;
// 判斷組件是否爲無狀態組件,無狀態組件沒有狀態更新隊列,它只專一於渲染
if (!shouldConstruct(Component) && (inst == null || inst.render == null)) {
renderedElement = inst
warnIfInvalidElement(Component, renderedElement)
inst = new StatelessComponent(Component)
}
// 這些初始化參數本應該在構造函數中設置,在此設置是爲了便於進行簡單的類抽象
inst.props = publicProps
inst.context = publicContext
inst.refs = emptyObject
inst.updater = ReactUpdateQueue
this._instance = inst
// 將實例存儲爲一個引用
ReactInstanceMap.set(inst, this)
// 初始化 state
var initialState = inst.state
if (initialState === undefined) {
inst.state = initialState = null
}
// 初始化更新隊列
this._pendingStateQueue = null
this._pendingReplaceState = false
this._pendingForceUpdate = false
var markup;
// 若是掛載出現錯誤
if (inst.upstable_handleError) {
markup = this.performInitialMountWithErrorHandling(renderedElement, nativeParent, nativeContainerInfo, transaction, context)
} else {
// 初始化掛載
markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction,
context)
}
// 若是存在 componentDidMount , 則調用
if (inst.componentDidMount) {
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst)
}
return markup
}
// 掛載錯誤處理
performInitialMountWithErrorHandling: function(renderedElement, nativeParent, nativeContainerInfo, transaction, context) {
var markup;
var checkpoint = transaction.checkpoint()
try {
// 捕捉錯誤,沒有錯誤則初始化掛載
markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction,
context)
} catch (e) {
transaction.rollback(checkpoint)
this._instance.unstable_handleError(e)
if (this._pendingStateQueue) {
this._instance.state = this._processPendingState(this._instance.props, this._instance.context)
}
checkpoint = transaction.checkpoint()
// 若是捕捉到錯誤,則執行 unmountComponent 後,再初始化掛載
this._renderedComponent.unmountComponent(true)
transaction.rollback(checkpoint)
markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context)
}
return markup
}
// 掛載組件
performInitialMount: function(renderedElement, nativeParent, nativeContainerInfo, transaction, context) {
var inst = this._instance
// 若是存在 componentWillMount, 則調用
if (inst.componentWillMount) {
inst.componentWillMount()
// componentWillMount 調用 setState 時,不會觸發 re-render 而是自動提早合併
if (this._pendingStateQueue) {
inst.state = this._processPendingState(inst.props, inst.context)
}
}
// 若是不是無狀態組件,便可開始渲染
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent()
}
this._renderedNodeType = ReactNodeTypes.getType(renderedElement)
// 獲得 _currentElement 對應的 component 類實例
this._renderedComponent = this._instantiateReactComponent(renderedElement)
// render 遞歸渲染
var markup = ReactReconciler.mountComponent(this._renderedComponent, transaction, nativeParent,
nativeContainerInfo, this._processChildContext(context))
return markup
}
複製代碼
總結一下 - 初次渲染 ?框架
1 . 當使用 ES6 classes 編寫 React 組件時,其實就是調用內部方法 createClass 建立組件, 該方法返回一個Constructor(props, context, updater) 用來生成組件實例,咱們發如今調用React.createClass,已經執行了getDefaultProps(),並將其賦值於Constructor的原型中less
2 . 因爲經過ReactCompositeComponentBase 返回的是一個虛擬節點,因此須要利用 instantiateReactComponent去獲得實例,再使用 mountComponent 拿到結果做爲當前自定義元素的結果
當使用 React 建立組件時,首先會調用 instantiateReactComponent,這是初始化組件的入口 函數,它經過判斷 node 類型來區分不一樣組件的入口 (具體看下邊說明)
3 . 在React中,由於全部class組件都要繼承自Component類或者PureComponent類,所以和原生class寫法同樣,要在constructor裏首先調用super方法,才能得到this。經過 mountComponent 掛載組件,初始化序號、標記等參數,判斷是否爲無狀態組件,並進行 對應的組件初始化工做,好比初始化 props、context 等參數。利用 getInitialState 獲取初始化 state、初始化更新隊列和更新狀態。
4 . 若存在 componentWillMount,則執行。若是此時在 componentWillMount 中調用 setState 方法,是不會觸發 re-render的,而是會進行 state 合併,且 inst.state = this._processPendingState (inst.props, inst.context) 是在 componentWillMount 以後執行的,所以 componentWillMount 中 的 this.state 並非最新的,在 render 中才能夠獲取更新後的 this.state。
React 是利用更新隊列 this._pendingStateQueue 以及更新狀態 this._pendingReplaceState 和 this._pendingForceUpdate 來實現 setState 的異步更新機制。也就是說 this.setState 最終調用的是this.updater.enqueueSetState方法
5 . 當渲染完成後,若存在 componentDidMount,則調用。其實,mountComponent 本質上是經過遞歸渲染內容的,因爲遞歸的特性,父組件的 componentWillMount 在其子組件的 componentWillMount 以前調用,而父組件的 componentDidMount 在其子組件的 componentDidMount 以後調用。
instantiateReactComponent 入口組件
· 當 node 爲空時,說明 node 不存在,則初始化空組件 ReactEmptyComponent.create(instantiateReactComponent)。
· 當 node 類型爲對象時,便是 DOM 標籤組件或自定義組件,那麼若是 element 類型爲字符串時 ,則初始化 DOM 標籤組件ReactNativeComponent.createInternalComponent (element),不然初始化自定義組件 ReactCompositeComponentWrapper()
· 當 node 類型爲字符串或數字時,則初始化文本組件 ReactNativeComponent.createInstanceForText(node)。
· 若是是其餘狀況,則不做處理
// instantiateReactComponent 方法源碼, 初始化組件入口
function instantiateReactComponent(node, parentCompositeType) {
var instance;
// 空組件 (ReactEmptyComponent)
if (node === null || node === false) {
instance = ReactEmptyComponent.create(instantiateReactComponent)
}
// 對象類型
if (typeof node === 'object') {
var element = node
if (typeof element === 'string') {
instance = ReactNativeComponent.createInternalComponent (element)
} else if (isInternalComponentType(element.type)) {
// 不是字符串表示的自定義組件暫沒法使用,此處將不作組件初始化操做
instance = new element.type(element)
} else {
// 自定義組件
instance = new ReactCompositeComponentWrapper()
}
} else if (typeof node === 'string' || typeof node === 'number') {
// 字符串或數字
instance = ReactNativeComponent.createInstanceForText(node)
} else {
// 不作處理
}
// 設置實例
instance.construct(node)
// 初始化參數
instance._mountIndex = 0
instance._mountImage = null
return instance
}
複製代碼
updateComponent 負責管理生命週期中的 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate
首先經過 updateComponent 更新組件,若是先後元素不一致,說明須要進行組件更新
若是存在 componentWillReceiveProps, 則執行。若是此時在 componentWillReceiveProps 中調 用 setState,是不會觸發 re-render 的,而是會進行 state 合併。且在 componentWillReceiveProps、 shouldComponentUpdate 和 componentWillUpdate 中也仍是沒法獲取到更新後的 this.state,即此 時訪問的 this.state 仍然是未更新的數據,須要設置 inst.state = nextState 後才能夠,所以 只有在 render 和 componentDidUpdate 中才能獲取到更新後的 this.state。
調用 shouldComponentUpdate 判斷是否須要進行組件更新,若是存在 componentWillUpdate, 則執行。
updateComponent 本質上也是經過遞歸渲染內容的,因爲遞歸的特性,父組件的 componentWillUpdate 是在其子組件的 componentWillUpdate 以前調用的,而父組件的 componentDidUpdate 也是在其子組件的 componentDidUpdate 以後調用的。
當渲染完成以後,若存在 componentDidUpdate,則觸發
// receiveComponent 是經過調用 updateComponent 進行組件更新的
receiveComponent: function(nextElement, transaction, nextContext) {
var prevElement = this._currentElement; var prevContext = this._context;
this._pendingElement = null;
this.updateComponent(transaction, prevElement, nextElement, prevContext, nextContext);
},
updateComponent: function(transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
var inst = this._instance
var willReceive = false
var nextContext
var nextProps
// 上下文是否改變
if (this._context === nextUnmaskedContext) {
nextContext = inst.context
} else {
nextContext = this._processContext(nextUnmaskedContext)
willReceive = true
}
if (preParentElement === nextParentElement) {
// 元素相同,跳過元素類型檢測
nextProps = nextParentElement.props
} else {
// 檢查元素的類型
nextProps = this._processProps(nextParentElement.props)
willReceive = true
}
// 若是存在 compnentWillReceiveProps ,則調用
if (inst.componentWillReceiveProps && willReceive) {
inst.componentWillReceiveProps(nextProps, nextContext)
}
// 將新的state合併到更新的隊列中, 此時的 nextState 是最新的 state
var nextState = this._processPendingState(nextProps, nextContext)
// 根據更新隊列和 shouldComponentUpdate 的狀態來判斷是否須要更新組件
var shouldUpdate = this._pendingForceUpdate || !inst.shouldComponentUpdate || inst.shouldComponentUpdate(nextProps, nextState, nextContext)
if (shouldUpdate) {
// 重置更新隊列
this._pendingForceUpdate = false
// 即將更新 this.props 、 this.state 、 this.context
this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext)
} else {
// 若是肯定組件不更新,那麼仍然要設置 props 和 state
this._currentElement = nextParentElement
this._context = nextUnmaskedContext
inst.props = nextProps
inst.state = nextState
inst.context = nextContext
}
},
// 當肯定組件須要更新時,則調用
_performComponentUpdate: function(nextElement, nextProps, nextState, nextContext, transaction, unmaskedContext) {
var inst = this._instance
var hasComponentDidUpdate = Boolean(inst.componentDidUpdate)
var preProps
var preState
var preContext
// 若是存在 componentDidUpdate , 則將當前的 state, props, context 保存一份
if (hasComponentDidUpdate) {
preProps = inst.props
preState = inst.state
preContext = inst.context
}
// 若是存在 componentWillUpdate ,則調用
if (inst.componentWillUpdate) {
inst.componetWillUpdate(nextProps, nextState, nextContext)
}
this._currentElement = nextParentElement
this._context = unmaskedContext
// 更新 this.props 、 this.state 、 this.context
inst.props = nextProps
inst.state = nextState
inst.context = nextContext
// 調用 render 渲染組件
this._updateRenderedComponent(transaction, unmaskedContext)
// 當組件完成更新後,若是存在 componentDidUpdate,則調用
if (hasComponentDidUpdate) {
transaction.getReactMountReady().enqueue(
inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),inst
)
}
}
// 調用 render 渲染組件
_updateRenderedComponent: function (transaction, context) {
var preComponentInstance = this._renderedComponet
var preRenderedElement = prevComponentInstance._currentElement
var nextRenderedElement = this._renderValidatedComonet()
// 若是須要更新,則調用ReactReconciler.receiveComponent 繼續更新組件
if (shouldUpdateReactComponent(preRenderedElement, nextRenderedElement)) {
ReactReconciler.receiveComponent(preComponentInstance, nextRenderedElement, transaction, this._processChildContext(context))
} else {
// 若是不須要更新, 則渲染組件
var oldNativeNode = ReactReconciler.getNativeNode(preComponentInstance)
ReactReconciler.unmountComponent(preComponentInstance)
this._renderedNodeType = ReactNodeTypes.getType(nextRenderedElement)
// 獲得 nextRenderedElement 對應的component 類實例
this._renderedComponet = this._instantiateReactComponent(nextRenderedElement)
// 使用 render 遞歸渲染
var nextMarkup = ReactReconciler.mountComponent(this._renderedComponent,transaction, this._nativeParent, this._nativeContainerInfo, this._processChildContext(context))
tgus._replaceNodeWithMarkup(oldNativeNode, nextMarkup)
}
}
複製代碼
禁止在 shouldComponentUpdate 和 componentWillUpdate 中調用this.setState,由於這樣會形成循環調用,直到耗光瀏覽器內存後奔潰,那麼爲何不能呢 ?
1 . 調用 setState 時,實際上會執行 enqueueSetState 方法,並對partialState和_pendingStateQueue更新隊列進行合併操做,最終經過 enqueueUpdate 執行 state 的更新
2 . 而 performUpdateIfNecessary 方法會獲取 _pendingElement、_pendingStateQueue、_pendingForceUpdate,並調用 receiveComponent 和 updateComponent 方法進行組件更新
3 . 如 果 在 shouldComponentUpdate 或 componentWillUpdate 方 法 中 調 用 setState , 此 時 this._pendingStateQueue != null,則 performUpdateIfNecessary 方法就會調用 updateComponent 方法進行組件更新,但 updateComponent 方法又會調用 shouldComponentUpdate 和 componentWill- Update 方法,所以形成循環調用,使得瀏覽器內存佔滿後崩潰
複製代碼
unmountComponent 負責管理生命週期中的 componentWillUnmount
若是存在 componentWillUnmount,則執行並重置全部相關參數、更新隊列以及更新狀態,如 果此時在 componentWillUnmount 中調用 setState,是不會觸發 re-render 的,這是由於全部更新 隊列和更新狀態都被重置爲 null,並清除了公共類,完成了組件卸載操做
unmountComponent: function(safely) {
if (!this._renderedComponent) {
return
}
var inst = this._instance
// 若是存在 componentWillUnmount, 則調用
if (inst.componentWillUnmount) {
if (safely) {
var name = this.getName() + '.componentWillUnmount()'
ReactErrorUtils.invokeGuardedCallback(name, inst.componentWillUnmount.bind(inst))
} else {
inst.componentWillUnmount()
}
}
// 若是組件已經渲染,則對組件進行 unmountComponent 操做
if (this._renderedComponent) {
ReactReconciler.unmountComponent(this._renderedComponent, safely)
this._renderedNodeType = null
this._renderedComponent = null
this._instance = null
}
// 重置相關參數、更新隊列以及更新狀態
this._pendingStateQueue = null // 更新隊列
this._pendingReplaceState = false // 更新狀態
this._pendingForceUpdate = false
this._pendingCallbacks = null
this._pendingElement = null
this._context = null
this._rootNodeID = null
this._topLevelWrapper = null
// 清除公共類
ReactInstanceMap.remove(inst)
}
複製代碼
在 React 開發中,一個很重要的原則就是讓組件儘量是無狀態的,無狀態組件沒有狀態,沒有生命週期,只是簡單地接受 props 渲染生成 DOM 結構,是一個 純粹爲渲染而生的組件。
Blog: blog.pengdaokuan.cn:4001
Github: github.com/PDKSophia/b…