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

前言:
接上篇 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, 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) 非第一次渲染階段(屢次渲染階段)html

① 執行updateHostComponent()方法,進行diff判斷哪些props是須要update的,將其push進該fiber對象的updateQueue(更新隊列)屬性中java

② 若是當前節點的ref指向有變更的話,執行markRef(),添加RefEffectTagnode

(2) 第一次渲染階段(暫不考慮server端渲染)react

① 執行createInstance(),建立fiber實例git

② 執行appendAllChildren(),插入全部子節點github

③ 執行finalizeInitialChildren(),初始化事件監聽,並執行markUpdate(),以添加UpdateEffectTag,以便在commit階段執行真正的DOM更新web

④ 將處理好的節點實例綁定到fiber對象的stateNode

⑤ 若是當前節點的ref指向有變更的話,執行markRef(),添加RefEffectTag

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

咱們來解析下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(),加上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;
}
複製代碼

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的更新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?

但願後面能有答案

5、補充
在我早期寫的一篇文章React之diff算法中,主要介紹了tree diffcomponent diffelement diff這三個diff策略,也是經過解析 React 源碼,才發現了第四個diff策略——prop diff,也就是本文所講的內容。

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

ReactDOMHostConfig.js
github.com/AttackXiaoJ…

ReactDOMComponent.js
github.com/AttackXiaoJ…

assertValidProps.js
github.com/AttackXiaoJ…


(完)

相關文章
相關標籤/搜索