The HostComponent renders the first rendering phase of the HostComponent. The HostComponent renders the first rendering phase of the HostComponent.

HostComponent updates DOM nodes

Source:

    // Update the DOM node, which involves the virtual DOM

    //https://zh-hans.reactjs.org/docs/faq-internals.html#___gatsby

    case HostComponent: {

      // Context is relevant, skip it for now

      // Only if contextFiber's current is the current fiber

      popHostContext(workInProgress);

      const rootContainerInstance = getRootHostContainer();

      / / = = = = = = = = = = = = = = = = = =

      // The node type, such as the
tag, corresponds to the fiber object of type "div".


      const type = workInProgress.type;

      // If not the first render

      if(current ! = =null&& workInProgress.stateNode ! =null) {

        // Diff judgment when DOM is updated

        / / update queue workInProgress updateQueue

        updateHostComponent(

          current,

          workInProgress,

          type,

          newProps,

          rootContainerInstance,

        );

        // if ref points to changes, update ref

        if (current.ref! == workInProgress.ref) {

          //// Add the EffectTag for Ref

          markRef(workInProgress);

        }

      }



      else {

        // Render for the first time



        // React might have an internal problem if there is no update for the new props, but the React function is running at this point

        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 is relevant, skip it for now

        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.

        // Whether it was a server render

        let wasHydrated = popHydrationState(workInProgress);

        // If it is server-side rendering, skip it for now

        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);

          }

        }

        // Not server-side rendering

        else {

          // Create fiber instance, i.e. DOM instance

          let instance = createInstance(

            type,

            newProps,

            rootContainerInstance,

            currentHostContext,

            workInProgress,

          );

          // Insert child nodes

          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 (

            // Initialize the event listener

            // If the node can autofocus

            finalizeInitialChildren(

              instance,

              type,

              newProps,

              rootContainerInstance,

              currentHostContext,

            )

          ) {

            // Add EffectTag for update during commit phase

            markUpdate(workInProgress);

          }

          // Bind the processed node instance to the stateNode

          workInProgress.stateNode = instance;

        }

        // if the ref reference is not empty

        if (workInProgress.ref! = =null) {

          // If there is a ref on a host node we need to schedule a callback

          // Add the EffectTag for Ref

          markRef(workInProgress);

        }

      }

      break;

    }

Copy the code

(1) Non-first render stage (multiple render stage)

(1) Execute the updateHostComponent() method to diff which props need to be updated and push them into the updateQueue property of the Fiber object

② If the ref point of the current node changes, execute markRef() to add the EffectTag of ref

(2) The first rendering stage (server side rendering is not considered for the time being)

① Run createInstance() to create an instance of Fiber

② Execute appendAllChildren() and insert all child nodes

③ Execute finalizeInitialChildren(), initialize event listening, and execute markUpdate() to add the EffectTag of the Update so that the actual DOM Update can be performed at the COMMIT stage

4. Bind the processed node instance to the stateNode of the Fiber object

⑤ If the ref pointer of the current node changes, execute markRef() to add the EffectTag of ref

Note: “first render phase” will be covered in the next article.

Let’s take a look at how HostComponent executes in multiple render phases

UpdateHostComponent role: to be the time to update the DOM prop diff judgment, get updates queue workInProgress. UpdateQueue

Source:

  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.

    / / the old props

    const oldProps = current.memoizedProps;

    // The memory address referenced by the old and new props objects has not changed

    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.

    // If the object is updated because of an update to the child node, there is no new props to update, but the new props must be reused



    // 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: it is better to use a different updateAPI to distinguish between self-updating and factor node updating



    // Get the DOM node instance

    const instance: Instance = workInProgress.stateNode;

    // Skip it for now

    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`.



    // Compare the update to get the set of props to be updated: updatepayLoad :Array

    const updatePayload = prepareUpdate(

      instance,

      type,

      oldProps,

      newProps,

      rootContainerInstance,

      currentHostContext,

    );

    // TODO: Type this specific to this type of component.

    // Assign the props set to be updated to the update queue

    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.

    / / note: even an empty array will add the Update EffectTag, such as input/option/select/textarea

    if (updatePayload) {

      markUpdate(workInProgress);

    }

  };

Copy the code

(1) If the memory address referenced by the new and old props object did not change, return ()

(2) Execute prepareUpdate() and compare the updates to obtain the set of props to be updated: updatepayLoad

(3) assign the props set to update to updateQueue: updateQueue

(4) If the Update set is not null, execute markUpdate() with the Update EffectTag

Note: markUpdate() is executed even if updatePayload is an empty array []

(5) A quick look at markUpdate() :

// Add the Update EffectTag

function markUpdate(workInProgress: Fiber{

  // Tag the fiber with an update effect. This turns a Placement into

  // a PlacementAndUpdate.

  workInProgress.effectTag |= Update;

}

Copy the code

PrepareUpdate: compare the set of props to be updated: updatepayLoad

Source:

// Compare the update to get the set of props to be updated

export function prepareUpdate(

  domElement: Instance,

  type: string,

  oldProps: Props,

  newProps: Props,

  rootContainerInstance: Container,

  hostContext: HostContext,

) :null | Array<mixed
{

  // Delete dev code



  // Calculate the difference between the old and new props

  //return updatepayload:Array

  return diffProperties(

    domElement,

    type,

    oldProps,

    newProps,

    rootContainerInstance,

  );

}

Copy the code

The React dev environment has other operations, but I removed the dev code.

4, diffProperties function: Calculate the difference between the old and new props, i.e. Prop Diff strategy

Source:

// Calculate the diff between the two objects.

// Calculate the difference between the old and new props

//return updatepayload:Array

export function diffProperties(

  domElement: Element,

  tag: string,

  lastRawProps: Object,

  nextRawProps: Object,

  rootContainerElement: Element | Document,

) :null | Array<mixed
{

  // Delete dev code



  // The props set to be updated

  let updatePayload: null | Array<any> = null;

  / / the old props

  let lastProps: Object;

  / / new props

  let nextProps: Object;

  / / input/option/select/textarea regardless if there is a change will update content

  switch (tag) {

    case 'input':

      // Get old props

      lastProps = ReactDOMInputGetHostProps(domElement, lastRawProps);

      // Get new 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;

      // If you need to update the bind click method

      if (

        typeoflastProps.onClick ! = ='function' &&

        typeof nextProps.onClick === 'function'

      ) {

        // TODO: This cast may not be sound for SVG, MathML or custom elements.

        // Initialize the onclick event for Safari mobile compatibility

        trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));

      }

      break;

  }

  // Determine whether a new attribute, such as style, is correctly assigned

  assertValidProps(tag, nextProps);



  let propKey;

  let styleName;

  let styleUpdates = null;



  // Loop over properties in the old props

  // Add the delete props to the array

  for (propKey in lastProps) {

    if (

      // If the new props has this property

      nextProps.hasOwnProperty(propKey) ||

      // Or if the old props doesn't have this property (an attribute on the prototype chain, such as toString())

! lastProps.hasOwnProperty(propKey) ||

      // or if the old props is 'null'

      lastProps[propKey] == null

    ) {

      // skip this loop, that is, skip this loop only if the if is false

      // The new props does not have this property and the old props has this property and it is not 'null'/null

      // That is, the propKey is the deleted property and can continue executing the following code

      continue;

    }



    // propKey is a new property

    


    if (propKey === STYLE) {

      // Get the old style property object

      const lastStyle = lastProps[propKey];

      // Iterate over the old style property, such as height

      for (styleName in lastStyle) {

        // If the old style already has styleName, reset it to "".

        if (lastStyle.hasOwnProperty(styleName)) {

          if(! styleUpdates) {

            styleUpdates = {};

          }

          // Reset (initialize)

          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.

    }

    // If there is a binding event

    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.

      // Push the propKey that does not meet the above criteria into updatePayload

      / / such as [' className, null]

      (updatePayload = updatePayload || []).push(propKey, null);

    }

  }



  // Loop the propKey of the new props

  for (propKey in nextProps) {

    // Get the value of the new prop

    const nextProp = nextProps[propKey];

    // Get the value of old props (undefined if old props is not traversed)

    constlastProp = lastProps ! =null ? lastProps[propKey] : undefined;

    if (

      // If the new props doesn't have that propKey (such as toString(), an attribute on the prototype chain)

! nextProps.hasOwnProperty(propKey) ||

      // The new value is equal to the old value.

      nextProp === lastProp ||

      // Both new and old values are "loosely equal" to null.

      // There is no update

      (nextProp == null && lastProp == null)

    ) {

      // Do not execute down

      // If the new props has the propKey and the new and old values are not null and are not equal

      // There is an update

      continue;

    }



    // The value of the new prop is different from that of the old prop



    

    if (propKey === STYLE) {

      // Delete dev code



      // If old props already has this prop

      if (lastProp) {

        // Unset styles on `lastProp` but not on `nextProp`.



        // If the new style does not have the CSS, set it to "" (i.e. delete the CSS property).

        for (styleName in lastProp) {

          if (

            lastProp.hasOwnProperty(styleName) &&

(! nextProp || ! nextProp.hasOwnProperty(styleName))

          ) {

            if(! styleUpdates) {

              styleUpdates = {};

            }

            // set it to ''

            styleUpdates[styleName] = ' ';

          }

        }

        // Update styles that changed since `lastProp`.

        // Update the style property

        for (styleName in nextProp) {

          if (

            // If the new props has a style and is different from the old props, update the style property

            nextProp.hasOwnProperty(styleName) &&

lastProp[styleName] ! == nextProp[styleName]

          ) {

            if(! styleUpdates) {

              styleUpdates = {};

            }

            / / update the style

            // Updates are uniformly placed in the styleUpdates object

            styleUpdates[styleName] = nextProp[styleName];

          }

        }

      }

      // If the style is not updated but added

      else {

        // Relies on `updateStylesByID` not mutating `styleUpdates`.

        // Initialization for the first time

        if(! styleUpdates) {

          if(! updatePayload) {

            updatePayload = [];

          }

          // insert 'style' and null push into the array updatePayload

          //['style',null]

          updatePayload.push(propKey, styleUpdates);

        }

        // Set styleUpdates to the new style value

        styleUpdates = nextProp;

        // The method has an if(styleUpdates) at the end, which pushes the situation:

        //['style',null,'style',{height:22,}]



      }

    }

    // __html

    else if (propKey === DANGEROUSLY_SET_INNER_HTML) {

      / / new innerHTML

      const nextHtml = nextProp ? nextProp[HTML] : undefined;

      / / old 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.

      }

    }

    // Update the child node

    //https://zh-hans.reactjs.org/docs/glossary.html#propschildren

    else if (propKey === CHILDREN) {

      if (

lastProp ! == nextProp &&

        // Child nodes are text nodes or numbers

        (typeof nextProp === 'string' || typeof nextProp === 'number')

      ) {

        //push into an array

        (updatePayload = updatePayload || []).push(propKey, ' ' + nextProp);

      }

    } else if (

      propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||

      propKey === SUPPRESS_HYDRATION_WARNING

    ) {

      // Noop

    }

    //// if there is a binding event, such as
{XXX})>


    else if (registrationNameModules.hasOwnProperty(propKey)) {

      // If there is a callback in the binding event

      if(nextProp ! =null) {

        // We eagerly listen to this even though we haven't committed yet.

        // Delete dev code



        React delegates events bound to nodes to document

        // The event section is involved

        // If you want to know immediately, please refer to:

        //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.

        // Special circumstances.

        // React needs to make sure that the pointer to the current props is updated before the listener is updated,

        // React therefore requires a commit (i.e. UpdatePayload) to ensure that the node can be updated



        // Therefore, updatePayload should not be null

        updatePayload = [];

      }

    }

    // New propsKey that does not match the above needs to be updated

    else {

      // For any other property we always add it to the queue and then we

      // filter it out using the whitelist during the commit.

      // Push the new propsKey into updatePayload



      // In the later commit phase, these props will be whitelisted

      (updatePayload = updatePayload || []).push(propKey, nextProp);

    }

  }



  // Push style updates into updatePayload

  if (styleUpdates) {

    // Delete dev code



    (updatePayload = updatePayload || []).push(STYLE, styleUpdates);

  }

  // similar to ['style',{height:14},'__html', XXXX...]

  // React doesn't use {style:{height:14}, '__html': XXX,}

  // This way to store the updated props?

  return updatePayload;

}

Copy the code

It’s a bit long.

① Switch () statement

(2) perform assertValidProps ()

③ Loop over properties in the old props

④ Loop through the properties in the new props

⑤ Push style updates into updatePayload

⑥ Return updatePayload to update the array


(1) the switch () statement to determine (1) whether the content of the input/option/select/textarea whether there is a change will update, namely updatePayload = [], they get old and new props way is different also, don’t fine

② The new and old props for other cases are the parameters passed in

(3) do compatible: follow trapClickOnNonInteractiveElement (), initialize the onclick event, so that is compatible with Safari mobile terminal

TrapClickOnNonInteractiveElement () :

// Initialize the onclick event for Safari mobile compatibility

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;

}

Copy the code

AssertValidProps () executes assertValidProps() to check whether the new properties, such as style, are properly assigned

AssertValidProps () :

// Determine whether a new attribute, such as style, is correctly assigned

function assertValidProps(tag: string, props: ? Object{

  if(! props) {

    return;

  }

  // Note the use of `==` which checks for null or undefined.

  // Check whether the label of the target node can contain child tags, such as

, , etc


  if (voidElementTags[tag]) {

    // Cannot contain child tags, error is reported

    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() : ' '.

    );

  }

  // If __html:"aaa" has child nodes, an error is reported

  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.'.

    );

  }

  // Delete dev code



  // If style is not null, but not Object, the following error is reported

  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() : ' '.

  );

}

Copy the code

① Determine whether the label of the target node can contain child tags. For example,

and cannot contain child tags

__html:”aaa”, __html:”aaa”

③ The style attribute is not null, but is not Object, error


(3) Perform a loop on the properties of the old props, adding any properties to the array that need to be deleted. (Old props does, new props does not.

If it is a deleted attribute, execute the following code

The following logic is that propKey is the action for the deleted property

Loop the CSS property in the style object if the propKey is a style property

If the old props has the CSS property, set it to an empty string “”

Such as:

<div style={{height:14,}}>aaa</div>

Copy the code

Set to

<div style={{height:"',}} >aaa</div>

Copy the code

(4) If there is a binding event, initialize the updatePayload array, indicating that it will update

RegistrationNameModules contain all sets of events and are printed to look like this:

⑤ Set propKey to null in all cases except in the code above, such as: className

updatePayload = ['className',null]    

Copy the code

(4) Loop through the properties of the new props, adding the new/updated props to the array

The following operations are for the new/updated props

If the propKey is a style property, loop the CSS property in the style object [1]. If the old style CSS property has a value, the new style object does not have the CSS property, delete the CSS property, such as:

<div style={{height:14,}}>aaa</div>

Copy the code

Set to

<div style={{height:"',}} >aaa</div>

Copy the code

[2] Update styleUpdates if the CSS property values in the new style are different from those in the old style, for example:

<div style={{height:14,}}>aaa</div>

Copy the code

Set to

<div style={{height:22,}}>aaa</div>

Copy the code

StyleUpdates as:

{

  height:22.

}

Copy the code

[3] If the style propKey is a new property, set styleUpdates directly to the value of the style object, as in:

<div>aaa</div>

Copy the code

Set to

<div style={{height:22,}}>aaa</div>

Copy the code

StyleUpdates as:

{

  height:22.

}

Copy the code

If the propKey is __html, compare the new and old innerHTML values and place them in the updatePayload update array

(3) If the propKey is children, push it directly into the updatePayload array when the child is text or number

④ If propKey is a binding event

[1] The binding event has a callback function, which executes ensureListeningTo() to find the Document object

React the purpose is to bind events in the history of the node unified entrusted to the document, want to know immediately, please refer to: www.cnblogs.com/Darlietooth…

[2] Initialize updatePayload to [], which is to update

⑤ The updated propKey is pushed into updatePayload except for the other cases above in the code


(5) Push style updates into updatePayload: There are three cases

① If it is a new style attribute

import React, {useEffect} from 'react';

import './App.css';



function App({

  const [styleObj, setStyleObj] = React.useState( null);



  useEffect((a)= >{

    setTimeout((a)= >{

      setStyleObj({height:14,})

    },2000)

}, [])



  return (

    <div className="App">

      <div style={styleObj}>

        aaa

      </div>

     </div>


  );

}



export default App;

Copy the code

UpdatePayload as:

'style'.null.'style', { height:14}]

Copy the code

② If it is the updated style attribute

import React, {useEffect} from 'react';

import './App.css';



function App({

  const [styleObj, setStyleObj] = React.useState({});



  useEffect((a)= >{

    setTimeout((a)= >{

      setStyleObj({height:14,})

    },2000)

}, [])



  return (

    <div className="App">

      <div style={styleObj}>

        aaa

      </div>

     </div>


  );

}



export default App;

Copy the code

UpdatePayload as:

'style', { height:14 } ]

Copy the code

③ If the style attribute is deleted

import React, {useEffect} from 'react';

import './App.css';



function App({

  const [styleObj, setStyleObj] = React.useState({height:14});



  useEffect((a)= >{

    setTimeout((a)= >{

      setStyleObj(null)

    },2000)

}, [])



  return (

    <div className="App">

      <div style={styleObj}>

        aaa

      </div>

     </div>


  );

}



export default App;

Copy the code

UpdatePayload as:

'style', { height:' '}]

Copy the code

(6) Finally return the updatePayload array, similar to

['style',{height:14},'__html',xxxx,.]

Copy the code

React doesn’t use {style:{height:14}, ‘__html’: XXX,} to store the updated props.

Hopefully there will be some answers later

The React diff algorithm (tree Diff, Component Diff, Element Diff, etc.) is a new diff algorithm. I discovered the fourth diff strategy, Prop Diff, which is what this article is about.

Six, making ReactFiberCompleteWork. Js: github.com/AttackXiaoJ…

ReactDOMHostConfig. Js: github.com/AttackXiaoJ…

ReactDOMComponent. Js: github.com/AttackXiaoJ…

AssertValidProps. Js: github.com/AttackXiaoJ…


(after)