前言:
接上篇 React源碼解析之completeWork和HostText的更新 ,本文講解下HostComponent
屢次渲染階段的更新(下篇講第一次渲染階段的更新)。javascript
1、HostComponent
做用:
更新DOM
節點php
源碼:css
//DOM 節點的更新,涉及到 virtual dom
//https://zh-hans.reactjs.org/docs/faq-internals.html#___gatsby
case HostComponent: {
//context 相關,暫時跳過
//只有當contextFiber的 current 是當前 fiber 時,纔會出棧
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
//==================
//節點類型,好比<div>標籤對應的 fiber 對象的 type 爲 "div"
const type = workInProgress.type;
//若是不是第一次渲染的話
if (current !== null && workInProgress.stateNode != null) {
//更新 DOM 時進行 diff 判斷
//獲取更新隊列 workInProgress.updateQueue
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance,
);
//ref指向有變更的話,更新 ref
if (current.ref !== workInProgress.ref) {
////添加 Ref 的 EffectTag
markRef(workInProgress);
}
}
else {
//若是是第一次渲染的話
//若是沒有新 props 更新,可是執行到這裏的話,多是 React 內部出現了問題
if (!newProps) {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
break;
}
//context 相關,暫時跳過
const currentHostContext = getHostContext();
// TODO: Move createInstance to beginWork and keep it on a context
// "stack" as the parent. Then append children as we go in beginWork
// or completeWork depending on we want to add then top->down or
// bottom->up. Top->down is faster in IE11.
//是否曾是服務端渲染
let wasHydrated = popHydrationState(workInProgress);
//若是是服務端渲染的話,暫時跳過
if (wasHydrated) {
// TODO: Move this and createInstance step into the beginPhase
// to consolidate.
if (
prepareToHydrateHostInstance(
workInProgress,
rootContainerInstance,
currentHostContext,
)
) {
// If changes to the hydrated node needs to be applied at the
// commit-phase we mark this as such.
markUpdate(workInProgress);
}
}
//不是服務端渲染
else {
//建立 fiber 實例,即 DOM 實例
let instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
//插入子節點
appendAllChildren(instance, workInProgress, false, false);
// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
if (
//初始化事件監聽
//若是該節點可以自動聚焦的話
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
//添加 EffectTag,方便在 commit 階段 update
markUpdate(workInProgress);
}
//將處理好的節點實例綁定到 stateNode 上
workInProgress.stateNode = instance;
}
//若是 ref 引用不爲空的話
if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
//添加 Ref 的 EffectTag
markRef(workInProgress);
}
}
break;
}
複製代碼
解析:
(1) 非第一次渲染階段(屢次渲染階段)html
① 執行updateHostComponent()
方法,進行diff
判斷哪些props
是須要update
的,將其push
進該fiber
對象的updateQueue
(更新隊列)屬性中java
② 若是當前節點的ref
指向有變更的話,執行markRef()
,添加Ref
的EffectTag
node
(2) 第一次渲染階段(暫不考慮server
端渲染)react
① 執行createInstance()
,建立fiber
實例git
② 執行appendAllChildren()
,插入全部子節點github
③ 執行finalizeInitialChildren()
,初始化事件監聽,並執行markUpdate()
,以添加Update
的EffectTag
,以便在commit
階段執行真正的DOM
更新web
④ 將處理好的節點實例綁定到fiber
對象的stateNode
上
⑤ 若是當前節點的ref
指向有變更的話,執行markRef()
,添加Ref
的EffectTag
注意:
「第一次渲染階段」放在下篇文章講。
咱們來解析下HostComponent
屢次渲染階段下的執行方法
2、updateHostComponent
做用:
更新DOM
時進行prop diff
判斷,獲取更新隊列workInProgress.updateQueue
源碼:
updateHostComponent = function(
current: Fiber,
workInProgress: Fiber,
type: Type,
newProps: Props,
rootContainerInstance: Container,
) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
//老 props
const oldProps = current.memoizedProps;
//新老 props 對象引用的內存地址沒有變過,即沒有更新
if (oldProps === newProps) {
// In mutation mode, this is sufficient for a bailout because
// we won't touch this node even if children changed.
return;
}
// If we get updated because one of our children updated, we don't
// have newProps so we'll have to reuse them.
// 若是該節點是由於子節點的更新而更新的,那麼是沒有新 props 須要更新的,但得複用新 props
// TODO: Split the update API as separate for the props vs. children.
// Even better would be if children weren't special cased at all tho.
//todo:用不一樣的 updateAPI 來區分自身更新和因子節點而更新,是更好的方式
//獲取 DOM 節點實例
const instance: Instance = workInProgress.stateNode;
//暫時跳過
const currentHostContext = getHostContext();
// TODO: Experiencing an error where oldProps is null. Suggests a host
// component is hitting the resume path. Figure out why. Possibly
// related to `hidden`.
//比較更新得出須要更新的 props 的集合:updatepayload:Array
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext,
);
// TODO: Type this specific to this type of component.
//將需更新的 props 集合賦值到 更新隊列上
workInProgress.updateQueue = (updatePayload: any);
// If the update payload indicates that there is a change or if there
// is a new ref we mark this as an update. All the work is done in commitWork.
//注意:即便是空數組也會加上 Update 的 EffectTag,如input/option/select/textarea
if (updatePayload) {
markUpdate(workInProgress);
}
};
複製代碼
解析:
(1) 若是新老props
對象引用的內存地址沒有變過,即沒有更新,則return
(2) 執行prepareUpdate()
,比較更新得出須要更新的 props 的集合:updatepayload
(3) 將需更新的props
集合賦值到「更新隊列:updateQueue
」上
(4) 若是更新集合不爲null
的話,執行markUpdate()
,加上Update
的EffectTag
注意:
即便updatePayload
爲空數組[ ]
,也會執行markUpdate()
(5) 簡單看下markUpdate()
:
//添加 Update 的 EffectTag
function markUpdate(workInProgress: Fiber) {
// Tag the fiber with an update effect. This turns a Placement into
// a PlacementAndUpdate.
workInProgress.effectTag |= Update;
}
複製代碼
3、prepareUpdate
做用:
比較更新得出須要更新的 props 的集合:updatepayload
源碼:
//比較更新得出須要更新的 props 的集合
export function prepareUpdate(
domElement: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): null | Array<mixed> {
//刪除了 dev 代碼
//計算出新老 props 的差別
//return updatepayload:Array
return diffProperties(
domElement,
type,
oldProps,
newProps,
rootContainerInstance,
);
}
複製代碼
解析:
主要是執行了diffProperties()
方法,可能你會有疑惑:爲何不直接把diffProperties()
放到外面去執行,由於 React 在 dev 環境有其餘的操做,可是我刪除了 dev 代碼。
4、diffProperties
做用:
計算出新老props
的差別,也就是prop diff
策略
源碼:
// Calculate the diff between the two objects.
//計算出新老 props 的差別
//return updatepayload:Array
export function diffProperties(
domElement: Element,
tag: string,
lastRawProps: Object,
nextRawProps: Object,
rootContainerElement: Element | Document,
): null | Array<mixed> {
//刪除了 dev 代碼
//須要更新的 props 集合
let updatePayload: null | Array<any> = null;
//老 props
let lastProps: Object;
//新 props
let nextProps: Object;
// input/option/select/textarea 不管內容是否有變化都會更新
switch (tag) {
case 'input':
//獲取老 props
lastProps = ReactDOMInputGetHostProps(domElement, lastRawProps);
//獲取新 props
nextProps = ReactDOMInputGetHostProps(domElement, nextRawProps);
updatePayload = [];
break;
case 'option':
lastProps = ReactDOMOptionGetHostProps(domElement, lastRawProps);
nextProps = ReactDOMOptionGetHostProps(domElement, nextRawProps);
updatePayload = [];
break;
case 'select':
lastProps = ReactDOMSelectGetHostProps(domElement, lastRawProps);
nextProps = ReactDOMSelectGetHostProps(domElement, nextRawProps);
updatePayload = [];
break;
case 'textarea':
lastProps = ReactDOMTextareaGetHostProps(domElement, lastRawProps);
nextProps = ReactDOMTextareaGetHostProps(domElement, nextRawProps);
updatePayload = [];
break;
default:
//oldProps
lastProps = lastRawProps;
//newProps
nextProps = nextRawProps;
//若是須要更新綁定 click 方法的話
if (
typeof lastProps.onClick !== 'function' &&
typeof nextProps.onClick === 'function'
) {
// TODO: This cast may not be sound for SVG, MathML or custom elements.
//初始化 onclick 事件,以便兼容Safari移動端
trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
}
break;
}
//判斷新屬性,好比 style 是否正確賦值
assertValidProps(tag, nextProps);
let propKey;
let styleName;
let styleUpdates = null;
//循環操做老 props 中的屬性
//將刪除 props 加入到數組中
for (propKey in lastProps) {
if (
//若是新 props 上有該屬性的話
nextProps.hasOwnProperty(propKey) ||
//或者老 props 沒有該屬性的話(即原型鏈上的屬性,好比:toString() )
!lastProps.hasOwnProperty(propKey) ||
//或者老 props 的值爲 'null' 的話
lastProps[propKey] == null
) {
//跳過這次循環,也就是說不跳過這次循環的條件是該 if 爲 false
//新 props 沒有該屬性而且在老 props 上有該屬性而且該屬性不爲 'null'/null
//也就是說,能繼續執行下面的代碼的前提是:propKey 是刪除的屬性
continue;
}
//能執行到這邊,說明 propKey 是新增屬性
//對 style 屬性進行操做,<div style={{height:30,}}></div>
if (propKey === STYLE) {
//獲取老的 style 屬性對象
const lastStyle = lastProps[propKey];
//遍歷老 style 屬性,如:height
for (styleName in lastStyle) {
//若是老 style 中原本就有 styleName 的話,則將其重置爲''
if (lastStyle.hasOwnProperty(styleName)) {
if (!styleUpdates) {
styleUpdates = {};
}
//重置(初始化)
styleUpdates[styleName] = '';
}
}
}
//dangerouslySetInnerHTML
//https://zh-hans.reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) {
// Noop. This is handled by the clear text mechanism.
}
//suppressHydrationWarning
//https://zh-hans.reactjs.org/docs/dom-elements.html#suppresshydrationwarning
//suppressContentEditableWarning
//https://zh-hans.reactjs.org/docs/dom-elements.html#suppresscontenteditablewarning
else if (
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING
) {
// Noop
}
else if (propKey === AUTOFOCUS) {
// Noop. It doesn't work on updates anyway.
}
//若是有綁定事件的話
else if (registrationNameModules.hasOwnProperty(propKey)) {
// This is a special case. If any listener updates we need to ensure
// that the "current" fiber pointer gets updated so we need a commit
// to update this element.
if (!updatePayload) {
updatePayload = [];
}
}
else {
// For all other deleted properties we add it to the queue. We use
// the whitelist in the commit phase instead.
//將不符合以上條件的刪除屬性 propKey push 進 updatePayload 中
//好比 ['className',null]
(updatePayload = updatePayload || []).push(propKey, null);
}
}
//循環新 props 的 propKey
for (propKey in nextProps) {
//獲取新 prop 的值
const nextProp = nextProps[propKey];
//獲取老 prop 的值(由於是根據新 props 遍歷的,因此老 props 沒有則爲 undefined)
const lastProp = lastProps != null ? lastProps[propKey] : undefined;
if (
//若是新 props 沒有該 propKey 的話( 好比原型鏈上的屬性,toString() )
!nextProps.hasOwnProperty(propKey) ||
//或者新 value 等於老 value 的話(即沒有更新)
nextProp === lastProp ||
//或者新老 value 均「寬鬆等於」 null 的話('null'還有其餘狀況嗎?)
//也就是沒有更新
(nextProp == null && lastProp == null)
) {
//不往下執行
//也就是說往下執行的條件是:新 props 有該 propKey 而且新老 value 不爲 null 且不相等
//即有更新的狀況
continue;
}
//能執行到這邊,說明新 prop 的值與老 prop 的值不相同/新增 prop 而且有值
//關於 style 屬性的更新 <input style={{xxx:yyy}}/>
if (propKey === STYLE) {
//刪除了 dev 代碼
//若是老 props 原本就有這個 prop 的話
if (lastProp) {
// Unset styles on `lastProp` but not on `nextProp`.
//若是新 style 沒有該 css 的話,將其置爲''(也就是刪掉該 css 屬性)
for (styleName in lastProp) {
if (
lastProp.hasOwnProperty(styleName) &&
(!nextProp || !nextProp.hasOwnProperty(styleName))
) {
if (!styleUpdates) {
styleUpdates = {};
}
//將其置爲''
styleUpdates[styleName] = '';
}
}
// Update styles that changed since `lastProp`.
//這裏纔是更新 style 屬性
for (styleName in nextProp) {
if (
//新 props 有 style 而且與老 props 不同的話,就更新 style 屬性
nextProp.hasOwnProperty(styleName) &&
lastProp[styleName] !== nextProp[styleName]
) {
if (!styleUpdates) {
styleUpdates = {};
}
//更新 style
//更新統一放在 styleUpdates 對象中
styleUpdates[styleName] = nextProp[styleName];
}
}
}
//若是不是更新的 style 而是新增的話
else {
// Relies on `updateStylesByID` not mutating `styleUpdates`.
//第一次初始化
if (!styleUpdates) {
if (!updatePayload) {
updatePayload = [];
}
//將 'style'、null push 進數組 updatePayload 中
//['style',null]
updatePayload.push(propKey, styleUpdates);
}
//styleUpdates 賦成新 style 的值
styleUpdates = nextProp;
//該方法最後有個 if(styleUpdates),會 push 這種狀況:
//['style',null,'style',{height:22,}]
}
}
// __html
else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
//新 innerHTML
const nextHtml = nextProp ? nextProp[HTML] : undefined;
//老 innerHTML
const lastHtml = lastProp ? lastProp[HTML] : undefined;
//push('__html','xxxxx')
if (nextHtml != null) {
if (lastHtml !== nextHtml) {
(updatePayload = updatePayload || []).push(propKey, '' + nextHtml);
}
} else {
// TODO: It might be too late to clear this if we have children
// inserted already.
}
}
//子節點的更新
//https://zh-hans.reactjs.org/docs/glossary.html#propschildren
else if (propKey === CHILDREN) {
if (
lastProp !== nextProp &&
//子節點是文本節點或數字
(typeof nextProp === 'string' || typeof nextProp === 'number')
) {
//push 進數組中
(updatePayload = updatePayload || []).push(propKey, '' + nextProp);
}
} else if (
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING
) {
// Noop
}
////若是有綁定事件的話,如<div onClick=(()=>{ xxx })></div>
else if (registrationNameModules.hasOwnProperty(propKey)) {
//綁定事件裏有回調函數的話
if (nextProp != null) {
// We eagerly listen to this even though we haven't committed yet.
//刪除了 dev 代碼
//找到 document 對象,React 是將節點上綁定的事件統一委託到 document 上的
//涉及到event 那塊了,暫時跳過
//想當即知道的,請參考:
//https://www.cnblogs.com/Darlietoothpaste/p/10039127.html?utm_source=tuicool&utm_medium=referral
ensureListeningTo(rootContainerElement, propKey);
}
if (!updatePayload && lastProp !== nextProp) {
// This is a special case. If any listener updates we need to ensure
// that the "current" props pointer gets updated so we need a commit
// to update this element.
//特殊的狀況.
//在監聽器更新前,React 須要確保當前 props 的指針獲得更新,
// 所以 React 須要一個 commit (即 updatePayload ),確保能更新該節點
//所以 updatePayload 要不爲 null
updatePayload = [];
}
}
//不符合以上的須要更新的新 propsKey
else {
// For any other property we always add it to the queue and then we
// filter it out using the whitelist during the commit.
//將新增的 propsKey push 進 updatePayload
//在以後的 commit 階段,會用白名單篩選出這些 props
(updatePayload = updatePayload || []).push(propKey, nextProp);
}
}
//將有關 style 的更新 push 進 updatePayload 中
if (styleUpdates) {
//刪除了 dev 代碼
(updatePayload = updatePayload || []).push(STYLE, styleUpdates);
}
//相似於['style',{height:14},'__html',xxxx,...]
//我很奇怪爲何 React 不用{style:{height:14}, '__html':xxx, }
//這種方式去存更新的 props?
return updatePayload;
}
複製代碼
解析:
有些長,總體結構是:
① switch()語句判斷
② 執行assertValidProps()
③ 循環操做老props
中的屬性
④ 循環操做新props
中的屬性
⑤ 將有關style
的更新push
進updatePayload
中
⑥ 最後返回updatePayload
更新數組
(1) switch()語句判斷
① 不管input/option/select/textarea
的內容是否有變化都會更新,即updatePayload = []
,它們獲取新老props
的方式也不同,不細講了
② 其餘狀況的新老props
是獲取的傳進來的參數
③ 作兼容:執行trapClickOnNonInteractiveElement()
,初始化onclick
事件,以便兼容Safari移動端
trapClickOnNonInteractiveElement()
:
//初始化 onclick 事件,以便兼容Safari移動端
export function trapClickOnNonInteractiveElement(node: HTMLElement) {
// Mobile Safari does not fire properly bubble click events on
// non-interactive elements, which means delegated click listeners do not
// fire. The workaround for this bug involves attaching an empty click
// listener on the target node.
// http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
// Just set it using the onclick property so that we don't have to manage any
// bookkeeping for it. Not sure if we need to clear it when the listener is
// removed.
// TODO: Only do this for the relevant Safaris maybe?
node.onclick = noop;
}
複製代碼
(2) 執行assertValidProps()
,判斷新屬性,好比style
是否正確賦值
assertValidProps()
:
//判斷新屬性,好比 style 是否正確賦值
function assertValidProps(tag: string, props: ?Object) {
if (!props) {
return;
}
// Note the use of `==` which checks for null or undefined.
//判斷目標節點的標籤是否能夠包含子標籤,如 <br/>、<input/> 等是不能包含子標籤的
if (voidElementTags[tag]) {
//不能包含子標籤,報出 error
invariant(
props.children == null && props.dangerouslySetInnerHTML == null,
'%s is a void element tag and must neither have `children` nor ' +
'use `dangerouslySetInnerHTML`.%s',
tag,
__DEV__ ? ReactDebugCurrentFrame.getStackAddendum() : '',
);
}
//__html設置的標籤內有子節點,好比:__html:"<span>aaa</span>" ,就會報錯
if (props.dangerouslySetInnerHTML != null) {
invariant(
props.children == null,
'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
);
invariant(
typeof props.dangerouslySetInnerHTML === 'object' &&
HTML in props.dangerouslySetInnerHTML,
'`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +
'Please visit https://fb.me/react-invariant-dangerously-set-inner-html ' +
'for more information.',
);
}
//刪除了 dev 代碼
//style 不爲 null,可是不是 Object 類型的話,報如下錯誤
invariant(
props.style == null || typeof props.style === 'object',
'The `style` prop expects a mapping from style properties to values, ' +
"not a string. For example, style={{marginRight: spacing + 'em'}} when " +
'using JSX.%s',
__DEV__ ? ReactDebugCurrentFrame.getStackAddendum() : '',
);
}
複製代碼
能夠看到,主要是如下 3 點的判斷:
① 判斷目標節點的標籤是否能夠包含子標籤,如<br/>
、<input/>
等是不能包含子標籤的
② 判斷__html
設置的標籤內是否有子節點,如:__html:"aaa" ,就會報錯
③ style
屬性不爲null
,但不是Object
類型的話,報錯
(3) 循環操做老props
中的屬性,將須要刪除的props
加入到數組中
① 若是不是刪除的屬性(老props
有,新props
沒有)的話,則跳過,不執行下面代碼
② 若是是刪除的屬性的話,則執行下方代碼
如下邏輯是propKey爲刪除的屬性的操做
③ 若是propKey
是style
屬性的話,循環style
對象中的CSS
屬性
若是老props
有該CSS
屬性的話,則將其值置爲空字符串''
好比:
<div style={{height:14,}}>aaa</div>
複製代碼
置爲
<div style={{height:'',}}>aaa</div>
複製代碼
④ 若是有綁定事件的話,則初始化updatePayload
數組,表示會更新
registrationNameModules
包含了全部的事件集合,打印出來是這個樣子:
⑤ 除了代碼中上述的其餘狀況,均將propKey
置爲null
好比:className
updatePayload = ['className',null]
複製代碼
(4) 循環操做新props
中的屬性,將新增/更新的props
加入到數組中
如下操做是針對新增/更新的props的
① 若是propKey
是style
屬性的話,循環style
對象中的CSS
屬性
[1] 若是老style
的CSS
屬性有值,新style
對象沒有該CSS
屬性,則刪除該CSS
屬性,好比:
<div style={{height:14,}}>aaa</div>
複製代碼
置爲
<div style={{height:'',}}>aaa</div>
複製代碼
[2] 若是新style
內的css
屬性的值與老style
內的值不一樣的話,更新styleUpdates
,好比:
<div style={{height:14,}}>aaa</div>
複製代碼
置爲
<div style={{height:22,}}>aaa</div>
複製代碼
則styleUpdates
爲:
{
height:22,
}
複製代碼
[3] 若是style
這個propKey
是新增屬性的話,則將styleUpdates
直接置爲style
對象的值,好比:
<div>aaa</div>
複製代碼
置爲
<div style={{height:22,}}>aaa</div>
複製代碼
則styleUpdates
爲:
{
height:22,
}
複製代碼
② 若是propKey
是__html
的話,比較新老innerHTML
的值,並放進updatePayload
更新數組中
③ 若是propKey
是children
的話
當子節點是文本或數字時,直接將其push
進updatePayload
數組中
④ 若是propKey
是綁定事件的話
[1] 綁定事件有回調函數,則執行ensureListeningTo()
,找到document
對象
React 這樣作的目的是,要將節點上綁定的事件統一委託到document
上,想當即知道的,請參考:
www.cnblogs.com/Darlietooth…
[2] 初始化updatePayload
爲[ ]
,也就是要更新
⑤ 除了代碼中上述的其餘狀況,均將更新的propKey
push 進 updatePayload 中
(5) 將有關 style 的更新 push 進 updatePayload 中
注意下這邊:有三種狀況
① 若是是新增的style
屬性
import React, {useEffect} from 'react';
import './App.css';
function App() {
const [styleObj, setStyleObj] = React.useState( null);
useEffect(()=>{
setTimeout(()=>{
setStyleObj({height:14,})
},2000)
},[])
return (
<div className="App">
<div style={styleObj}>
aaa
</div>
</div>
);
}
export default App;
複製代碼
則updatePayload爲:
[ 'style', null, 'style', { height:14 } ]
複製代碼
② 若是是更新的style
屬性
import React, {useEffect} from 'react';
import './App.css';
function App() {
const [styleObj, setStyleObj] = React.useState({});
useEffect(()=>{
setTimeout(()=>{
setStyleObj({height:14,})
},2000)
},[])
return (
<div className="App">
<div style={styleObj}>
aaa
</div>
</div>
);
}
export default App;
複製代碼
則updatePayload
爲:
[ 'style', { height:14 } ]
複製代碼
③ 若是是刪除的style
屬性
import React, {useEffect} from 'react';
import './App.css';
function App() {
const [styleObj, setStyleObj] = React.useState({height:14,});
useEffect(()=>{
setTimeout(()=>{
setStyleObj(null)
},2000)
},[])
return (
<div className="App">
<div style={styleObj}>
aaa
</div>
</div>
);
}
export default App;
複製代碼
則updatePayload
爲:
[ 'style', { height:'' } ]
複製代碼
(6) 最終返回updatePayload
數組,相似於
['style',{height:14},'__html',xxxx,...]
複製代碼
我很奇怪爲何 React 不用{style:{height:14}, '__html':xxx, }
這種方式去存更新的 props?
但願後面能有答案
5、補充
在我早期寫的一篇文章React之diff算法中,主要介紹了tree diff
、component diff
、element diff
這三個diff
策略,也是經過解析 React 源碼,才發現了第四個diff
策略——prop diff
,也就是本文所講的內容。
6、GitHubReactFiberCompleteWork.js
:
github.com/AttackXiaoJ…
ReactDOMHostConfig.js
:
github.com/AttackXiaoJ…
ReactDOMComponent.js
:
github.com/AttackXiaoJ…
assertValidProps.js
:
github.com/AttackXiaoJ…
(完)