接上篇 React源码解析之completeWork和HostText的更新 ,本文讲解下HostComponent
多次渲染阶段的更新(下篇讲第一次渲染阶段的更新)。
作用:
更新DOM
节点
源码:
//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) 非第一次渲染阶段(多次渲染阶段)
① 执行updateHostComponent()
方法,进行diff
判断哪些props
是需要update
的,将其push
进该fiber
对象的updateQueue
(更新队列)属性中
② 如果当前节点的ref
指向有变动的话,执行markRef()
,添加Ref
的EffectTag
(2) 第一次渲染阶段(暂不考虑server
端渲染)
① 执行createInstance()
,创建fiber
实例
② 执行appendAllChildren()
,插入所有子节点
③ 执行finalizeInitialChildren()
,初始化事件监听,并执行markUpdate()
,以添加Update
的EffectTag
,以便在commit
阶段执行真正的DOM
更新
④ 将处理好的节点实例绑定到fiber
对象的stateNode
上
⑤ 如果当前节点的ref
指向有变动的话,执行markRef()
,添加Ref
的EffectTag
注意: 「第一次渲染阶段」放在下篇文章讲。
我们来解析下HostComponent
多次渲染阶段下的执行方法
作用:
更新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;
}
作用:
比较更新得出需要更新的 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 代码。
作用:
计算出新老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
上,想立即知道的,请参考:
https://www.cnblogs.com/Darlietoothpaste/p/10039127.html?utm_source=tuicool&utm_medium=referral
[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?
希望后面能有答案
在我早期写的一篇文章 React之diff算法 中,主要介绍了tree diff
、component diff
、element diff
这三个diff
策略,也是通过解析 React 源码,才发现了第四个diff
策略——prop diff
,也就是本文所讲的内容。
ReactFiberCompleteWork.js
:
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberCompleteWork.js
ReactDOMHostConfig.js
:
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-dom/src/client/ReactDOMHostConfig.js
ReactDOMComponent.js
:
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-dom/src/client/ReactDOMComponent.js
assertValidProps.js
:
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-dom/src/shared/assertValidProps.js