前言
在上篇文章中,咱们讲了「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…
(完)