前言
在上篇文章中,咱們講了「mutation
」子階段的插入(Placement
)操做,接下來咱們講更新(Update
)和刪除(Deletion
)操做:javascript
//替換並更新該節點是Placement和Update的結合,就不講了
case PlacementAndUpdate: {
// Placement
//針對該節點及子節點進行插入操做
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
nextEffect.effectTag &= ~Placement;
// Update
const current = nextEffect.alternate;
//對 DOM 節點上的屬性進行更新
commitWork(current, nextEffect);
break;
}
//更新節點
//舊節點->新節點
case Update: {
const current = nextEffect.alternate;
//對 DOM 節點上的屬性進行更新
commitWork(current, nextEffect);
break;
}
case Deletion: {
//刪除節點
commitDeletion(nextEffect);
break;
}
複製代碼
1、commitWork()
做用:
對DOM
節點上的屬性進行更新html
源碼:java
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
//由於是執行 DOM 操做,因此supportsMutation爲 true,下面這一段不看
if (!supportsMutation) {
//刪除了本狀況代碼
}
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
// Note: We currently never use MountMutation, but useLayout uses
// UnmountMutation.
//循環 FunctionComponent 上的 effect 鏈,
//根據hooks 上每一個 effect 上的 effectTag,執行destroy/create 操做(相似於 componentDidMount/componentWillUnmount)
//詳情請看:[React源碼解析之Commit第一子階段「before mutation」](https://mp.weixin.qq.com/s/YtgEVlZz1i5Yp87HrGrgRA)中的「3、commitHookEffectList()」
commitHookEffectList(UnmountMutation, MountMutation, finishedWork);
return;
}
case ClassComponent: {
return;
}
//DOM 節點的話
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
if (instance != null) {
// Commit the work prepared earlier.
//待更新的屬性
const newProps = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
//舊的屬性
const oldProps = current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
// TODO: Type the updateQueue to be specific to host components.
//須要更新的屬性的集合
//好比:['style',{height:14},'__html',xxxx,...]
//關於updatePayload,請看:
// [React源碼解析之HostComponent的更新(上)](https://juejin.im/post/5e5c5e1051882549003d1fc7)中的「4、diffProperties」
const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
finishedWork.updateQueue = null;
//進行節點的更新
if (updatePayload !== null) {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
}
}
return;
}
case HostText: {
invariant(
finishedWork.stateNode !== null,
'This should have a text node initialized. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
const textInstance: TextInstance = finishedWork.stateNode;
const newText: string = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldText: string =
current !== null ? current.memoizedProps : newText;
//源碼即:textInstance.nodeValue = newText;
commitTextUpdate(textInstance, oldText, newText);
return;
}
case HostRoot: {
return;
}
case Profiler: {
return;
}
case SuspenseComponent: {
commitSuspenseComponent(finishedWork);
attachSuspenseRetryListeners(finishedWork);
return;
}
case SuspenseListComponent: {
attachSuspenseRetryListeners(finishedWork);
return;
}
case IncompleteClassComponent: {
return;
}
case EventComponent: {
return;
}
default: {
invariant(
false,
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
}
複製代碼
解析:
(1) 由於是執行DOM
操做,因此supportsMutation
爲true
,下面這一段不看:node
if (!supportsMutation) {
//刪除了本狀況代碼
}
複製代碼
(2) 主體邏輯是根據目標fiber
的tag
類型,進行不一樣的操做:
① 若是tag
是函數組件FunctionComponent
的話,則執行commitHookEffectList()
方法,做用是:react
循環FunctionComponent
上的effect
鏈,根據hooks
上每一個effect
上的effectTag
,執行destroy/create
操做(相似於componentDidMount
/componentWillUnmount
)git
關於commitHookEffectList()
的源碼,請看:
React源碼解析之Commit第一子階段「before mutation」中的「3、commitHookEffectList()
」github
② 若是tag
是DOM節點HostComponent
的話,則獲取要更新的屬性newProps
、舊屬性oldProps
和要更新的屬性集合updatePayload
,並執行commitUpdate()
,進行更新web
補充:
關於updatePayload
更新隊列是如何生成的,請看:
React源碼解析之HostComponent的更新(上)中的「4、diffProperties
」數組
③ 若是tag
是text
文本節點HostText
的話,則比較簡單了,執行commitTextUpdate()
,源碼就是替換文本值:app
export function commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.nodeValue = newText;
}
複製代碼
接下來,咱們就看下DOM
節點的更新—commitUpdate()
方法
2、commitUpdate()
做用:
進行DOM
節點的更新
源碼:
export function commitUpdate(
domElement: Instance,
updatePayload: Array<mixed>,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Update the props handle so that we know which props are the ones with
// with current event handlers.
//掛載屬性:node[internalEventHandlersKey] = props;
updateFiberProps(domElement, newProps);
// Apply the diff to the DOM node.
//更新 DOM 屬性
updateProperties(domElement, updatePayload, type, oldProps, newProps);
}
複製代碼
解析:
(1) 執行updateFiberProps()
,將待更新的屬性掛載到fiber
對象的internalEventHandlersKey
屬性上
updateFiberProps()
的源碼以下:
const randomKey = Math.random().toString(36).slice(2)
const internalEventHandlersKey = '__reactEventHandlers$' + randomKey
export function updateFiberProps(node, props) {
node[internalEventHandlersKey] = props;
}
複製代碼
(2) 執行updateProperties()
,更新DOM
屬性
3、updateProperties()
做用:diff prop
操做,找出DOM
節點上屬性的不一樣,以更新
源碼:
// Apply the diff.
//diff prop,找出DOM 節點上屬性的不一樣,以更新
export function updateProperties(
domElement: Element,
updatePayload: Array<any>,
tag: string,
lastRawProps: Object,
nextRawProps: Object,
): void {
// Update checked *before* name.
// In the middle of an update, it is possible to have multiple checked.
// When a checked radio tries to change name, browser makes another radio's checked false.
//若是是 radio 標籤的話
if (
tag === 'input' &&
nextRawProps.type === 'radio' &&
nextRawProps.name != null
) {
//單選按鈕的相關操做,可不看
ReactDOMInputUpdateChecked(domElement, nextRawProps);
}
//判斷是不是自定義的 DOM 標籤,具體請看:
//[React源碼解析之HostComponent的更新(下)](https://mp.weixin.qq.com/s/aB8jRVFzJ6EkkIqPVF3r1Q)中的「8、setInitialProperties」
//以前是不是自定義標籤
const wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
//待更新的是不是自定義標籤
const isCustomComponentTag = isCustomComponent(tag, nextRawProps);
// Apply the diff.
updateDOMProperties(
domElement,
updatePayload,
wasCustomComponentTag,
isCustomComponentTag,
);
// TODO: Ensure that an update gets scheduled if any of the special props
// changed.
//特殊標籤的特殊處理,可不看
switch (tag) {
case 'input':
// Update the wrapper around inputs *after* updating props. This has to
// happen after `updateDOMProperties`. Otherwise HTML5 input validations
// raise warnings and prevent the new value from being assigned.
ReactDOMInputUpdateWrapper(domElement, nextRawProps);
break;
case 'textarea':
ReactDOMTextareaUpdateWrapper(domElement, nextRawProps);
break;
case 'select':
// <select> value update needs to occur after <option> children
// reconciliation
ReactDOMSelectPostUpdateWrapper(domElement, nextRawProps);
break;
}
}
複製代碼
解析:
(1) 一些特殊標籤的特殊處理就不細說了
(2) 關於isCustomComponent()
,判斷是不是自定義的 DOM 標籤的源碼,請看:
React源碼解析之HostComponent的更新(下)中的「8、setInitialProperties
」
接下來重點看下updateDOMProperties()
,也就是DOM
節點屬性更新的核心源碼
4、updateDOMProperties()
做用:
進行DOM
節點的更新
源碼:
function updateDOMProperties(
domElement: Element,
updatePayload: Array<any>,
wasCustomComponentTag: boolean,
isCustomComponentTag: boolean,
): void {
// TODO: Handle wasCustomComponentTag
//遍歷更新隊列,注意 i=i+2,由於 updatePayload 是這樣的:['style',{height:14},'__html',xxxx,...]
//關於updatePayload,請看:
// [React源碼解析之HostComponent的更新(上)](https://juejin.im/post/5e5c5e1051882549003d1fc7)中的「4、diffProperties」
for (let i = 0; i < updatePayload.length; i += 2) {
//要更新的屬性
const propKey = updatePayload[i];
//要更新的值
const propValue = updatePayload[i + 1];
//要更新style 屬性的話,則執行setValueForStyles
if (propKey === STYLE) {
// 設置 style 的值,請看:
// [React源碼解析之HostComponent的更新(下)](https://juejin.im/post/5e65f86f6fb9a07cdc600e09)中的「8、setInitialProperties」中的第八點
setValueForStyles(domElement, propValue);
}
else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
// 設置innerHTML屬性,請看:
// [React源碼解析之HostComponent的更新(下)](https://juejin.im/post/5e65f86f6fb9a07cdc600e09)中的「8、setInitialProperties」中的第八點
setInnerHTML(domElement, propValue);
} else if (propKey === CHILDREN) {
//設置textContent屬性,請看:
// [React源碼解析之HostComponent的更新(下)](https://juejin.im/post/5e65f86f6fb9a07cdc600e09)中的「8、setInitialProperties」中的第八點
setTextContent(domElement, propValue);
} else {
//爲DOM節點設置屬性值,即 setAttribute
setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
}
}
}
複製代碼
解析:
邏輯也簡單,遍歷更新隊列,對不一樣的屬性,進行不一樣的操做
總共進行了 4 種狀況的操做:
(1) 對style
屬性,執行setValueForStyles()
,來設置style
的值
關於setValueForStyles()
的講解·,請看:
React源碼解析之HostComponent的更新(下)中的「8、setInitialProperties
」中的第八點
(2) 對innerHTML
屬性,執行setInnerHTML()
,來設置innerHTML
的值
關於setInnerHTML()
的講解·,請看:
React源碼解析之HostComponent的更新(下)中的「8、setInitialProperties」中的第八點
(3) 對children
屬性,即設置 DOM 標籤內部的值,執行setTextContent()
,來設置textContent
屬性
關於setTextContent()
的講解·,請看:
React源碼解析之HostComponent的更新(下)中的「8、setInitialProperties」中的第八點
(4) 除此以外的狀況,就是爲DOM
節點設置屬性值的狀況,好比className
,則執行setValueForProperty()
,也就是調用setAttribute
方法,就不解析了,放下源碼:
export function setValueForProperty(
node: Element,
name: string,
value: mixed,
isCustomComponentTag: boolean,
) {
const propertyInfo = getPropertyInfo(name);
if (shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag)) {
return;
}
if (shouldRemoveAttribute(name, value, propertyInfo, isCustomComponentTag)) {
value = null;
}
// If the prop isn't in the special list, treat it as a simple attribute.
if (isCustomComponentTag || propertyInfo === null) {
if (isAttributeNameSafe(name)) {
const attributeName = name;
if (value === null) {
node.removeAttribute(attributeName);
} else {
node.setAttribute(attributeName, '' + (value: any));
}
}
return;
}
const {mustUseProperty} = propertyInfo;
if (mustUseProperty) {
const {propertyName} = propertyInfo;
if (value === null) {
const {type} = propertyInfo;
(node: any)[propertyName] = type === BOOLEAN ? false : '';
} else {
// Contrary to `setAttribute`, object properties are properly
// `toString`ed by IE8/9.
(node: any)[propertyName] = value;
}
return;
}
// The rest are treated as attributes with special cases.
const {attributeName, attributeNamespace} = propertyInfo;
if (value === null) {
node.removeAttribute(attributeName);
} else {
const {type} = propertyInfo;
let attributeValue;
if (type === BOOLEAN || (type === OVERLOADED_BOOLEAN && value === true)) {
attributeValue = '';
} else {
// `setAttribute` with objects becomes only `[object]` in IE8/9,
// ('' + value) makes it output the correct toString()-value.
attributeValue = '' + (value: any);
if (propertyInfo.sanitizeURL) {
sanitizeURL(attributeValue);
}
}
if (attributeNamespace) {
node.setAttributeNS(attributeNamespace, attributeName, attributeValue);
} else {
node.setAttribute(attributeName, attributeValue);
}
}
}
複製代碼
總結
① 文本節點,執行textInstance.nodeValue = newText;
,來替換文本值
② DOM標籤,遍歷更新隊列updatePayload(['style',{height:14},'__html',xxxx,...]
),針對style
、innerHTML
、children
和attribute
進行屬性更新
GitHubcommitWork()
:
github.com/AttackXiaoJ…
commitUpdate()
:
github.com/AttackXiaoJ…
updateDOMProperties()
:
github.com/AttackXiaoJ…
setValueForProperty()
:
github.com/AttackXiaoJ…
(完)