React源码解析之HostComponent的更新(上)

2,918

前言:
接上篇 React源码解析之completeWork和HostText的更新 ,本文讲解下HostComponent多次渲染阶段的更新(下篇讲第一次渲染阶段的更新)。

一、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, falsefalse);

          // 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(),添加RefEffectTag

(2) 第一次渲染阶段(暂不考虑server端渲染)

① 执行createInstance(),创建fiber实例

② 执行appendAllChildren(),插入所有子节点

③ 执行finalizeInitialChildren(),初始化事件监听,并执行markUpdate(),以添加UpdateEffectTag,以便在commit阶段执行真正的DOM更新

④ 将处理好的节点实例绑定到fiber对象的stateNode

⑤ 如果当前节点的ref指向有变动的话,执行markRef(),添加RefEffectTag

注意:
「第一次渲染阶段」放在下篇文章讲。

我们来解析下HostComponent多次渲染阶段下的执行方法

二、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(),加上UpdateEffectTag

注意:
即使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;
}

三、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 代码。

四、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的更新pushupdatePayload

⑥ 最后返回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为删除的属性的操作

③ 如果propKeystyle属性的话,循环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的

① 如果propKeystyle属性的话,循环style对象中的CSS属性
[1] 如果老styleCSS属性有值,新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更新数组中

③ 如果propKeychildren的话
当子节点是文本或数字时,直接将其pushupdatePayload数组中

④ 如果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?

希望后面能有答案

五、补充
在我早期写的一篇文章React之diff算法中,主要介绍了tree diffcomponent diffelement diff这三个diff策略,也是通过解析 React 源码,才发现了第四个diff策略——prop diff,也就是本文所讲的内容。

六、GitHub
ReactFiberCompleteWork.js
github.com/AttackXiaoJ…

ReactDOMHostConfig.js
github.com/AttackXiaoJ…

ReactDOMComponent.js
github.com/AttackXiaoJ…

assertValidProps.js
github.com/AttackXiaoJ…


(完)