preface

The most effective way to learn is to share your knowledge with others.

Originally a very simple question, but I feel that I should remember it, it involves a lot of details can become a knowledge blind area.

Render multiple components

We double each item in the array using the map() function, and then we get a new list double and print it:

const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map((number) => number * 2)
console.log(doubled)
Copy the code

The code is printed [2, 4, 6, 8, 10]. In React, the process of converting an array to a list of elements is similar.

You can build a collection of elements within JSX by using {}.

Next, we use the map() method in Javascript to iterate over the numbers array. Change each element in the array to a

  • tag, and finally we assign the resulting array to listItems:
  • const numbers = [1, 2, 3, 4, 5]
    const listItems = numbers.map((number) =>
      <li>{number}</li>
    )
    Copy the code

    We insert the entire listItems into the

      element and render it into the DOM:
    ReactDOM.render(
      <ul>{listItems}</ul>,
      document.getElementById('root')
    )
    Copy the code

    This code generates a bulleted list from 1 to 5.

    Base list component

    Usually you need to render the list in a component.

    We can reconstitute the previous example as a component that takes the numbers array as an argument and outputs a list of elements.

    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        <li>{number}</li>
      );
      return (
        <ul>{listItems}</ul>
      );
    }
    
    const numbers = [1, 2, 3, 4, 5];
    ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
    )
    Copy the code

    When we run this code, we’ll see a warning that a key should be provided for list items, which means that when you create an element, you must include a special key property.

    Let’s assign a key attribute to each list element to address the warning above:

    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        <li key={number.toString()}>
          {number}
        </li>
      );
      return (
        <ul>{listItems}</ul>
      );
    }
    
    const numbers = [1, 2, 3, 4, 5];
    ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
    )
    Copy the code

    key

    Key helps React identify which elements have changed, such as being added or removed. So you should give each element in the array a definite identity.

    const numbers = [1, 2, 3, 4, 5];
    const listItems = numbers.map((number) =>
      <li key={number.toString()}>
        {number}
      </li>
    )
    Copy the code

    An element’s key should ideally be a unique string that the element has in the list. In general, we use the id in the data as the element key:

    const todoItems = todos.map((todo) =>
      <li key={todo.id}>
        {todo.text}
      </li>
    )
    Copy the code

    When an element has no specified ID, as a last resort you can use the element index index as key:

    const todoItems = todos.map((todo, index) =>
      // Only do this if items have no stable IDs
      <li key={index}>
        {todo.text}
      </li>
    )
    Copy the code

    If the order of list items may change, we do not recommend using an index as a key value, as this can lead to poor performance and possibly component state issues. Check out Robin Pokorny’s in-depth analysis of the negative effects of using indexes as keys. If you choose not to specify an explicit key, React will default to using the index as the key for list items.

    If you’re interested in learning more, here’s an article that delves into why keys are necessary.

    The lack ofkeyCommon warnings are shown below:

    Extract components with keys

    The element’s key makes sense only if it is placed in the context of the nearest array.

    For example, if you extract a ListItem component, you should keep the key on the element in the array, not on the

  • element in the ListItem component.
  • Example: Incorrect use of key

    function ListItem(props) { const value = props.value; Return (// error! You don't need to specify key here: <li key={value.tostring ()}> {value} </li>); } function NumberList(props) { const numbers = props.numbers; Const listItems = numbers. Map ((number) => // error! The key of the element should be specified here: <ListItem value={number} />); return ( <ul> {listItems} </ul> ); } const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( <NumberList numbers={numbers} />, document.getElementById('root') );Copy the code

    Example: The correct way to use key

    Function ListItem(props) {// Correct! Return <li>{props. Value}</li>; } function NumberList(props) { const numbers = props.numbers; Const listItems = numbers. Map ((number) => Key should be specified in the context of an array <ListItem key={number.tostring ()} value={number} />); return ( <ul> {listItems} </ul> ); } const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( <NumberList numbers={numbers} />, document.getElementById('root') )Copy the code

    A good rule of thumb is that elements in the map() method need a key attribute.

    The key must only be unique between sibling nodes

    A key used in an array element should be unique among its siblings. However, they need not be globally unique. When we generate two different arrays, we can use the same key value:

    function Blog(props) {
      const sidebar = (
        <ul>
          {props.posts.map((post) =>
            <li key={post.id}>
              {post.title}
            </li>
          )}
        </ul>
      );
      const content = props.posts.map((post) =>
        <div key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.content}</p>
        </div>
      );
      return (
        <div>
          {sidebar}
          <hr />
          {content}
        </div>
      );
    }
    
    const posts = [
      {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
      {id: 2, title: 'Installation', content: 'You can install React from npm.'}
    ];
    ReactDOM.render(
      <Blog posts={posts} />,
      document.getElementById('root')
    )
    Copy the code

    The key will pass information to React, but not to your components. If you need the value of the key attribute in your component, pass it explicitly with another attribute name:

    const content = posts.map((post) =>
      <Post
        key={post.id}
        id={post.id}
        title={post.title} />
    )
    Copy the code

    In the example above, the Post component can read props. Id, but not props. Key.

    Embed map() in JSX

    In the above example, we declared a single listItems variable and included it in JSX: listItems = listItems;

    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        <ListItem key={number.toString()}
                  value={number} />
      );
      return (
        <ul>
          {listItems}
        </ul>
      );
    }
    Copy the code

    JSX allows any expression to be embedded in braces, so we can inline the result returned by map() :

    function NumberList(props) {
      const numbers = props.numbers;
      return (
        <ul>
          {numbers.map((number) =>
            <ListItem key={number.toString()}
                      value={number} />
          )}
        </ul>
      );
    }
    Copy the code

    Sometimes this can make your code clearer, but sometimes this style can be abused. Just as in JavaScript, it’s entirely up to you when you need to extract a variable for readability. But keep in mind that if a map() has too many levels nested, that might be a good time to extract components.

    Recurse on the child nodes

    By default, React iterates through lists of both child elements while recursively iterating through the child elements of a DOM node. When a difference occurs, a mutation is generated.

    When new elements are added at the end of the child element list, the update overhead is low. Such as:

    <ul>
      <li>first</li>
      <li>second</li>
    </ul>
    
    <ul>
      <li>first</li>
      <li>second</li>
      <li>third</li>
    </ul>
    Copy the code

    React matches two

  • first
  • trees, then matches the second< li>second tree, and finally inserts the

  • third
  • tree of the third element.

    If you simply insert the new element into the table header, the update will be expensive. Such as:

    <ul>
      <li>Duke</li>
      <li>Villanova</li>
    </ul>
    
    <ul>
      <li>Connecticut</li>
      <li>Duke</li>
      <li>Villanova</li>
    </ul>
    Copy the code

    React will not realize that it should keep

  • Duke
  • and

  • Villanova
  • , but will rebuild each child element. This situation can cause performance problems.

    Keys

    To address these issues, React supports the key attribute. When a child element has a key, React uses the key to match the child element of the original tree with the child element of the latest tree. The following example makes a previously inefficient conversion efficient after adding a key:

     <ul>
      <li key="2015">Duke</li>
      <li key="2016">Villanova</li>
    </ul>
    
    <ul>
      <li key="2014">Connecticut</li>
      <li key="2015">Duke</li>
      <li key="2016">Villanova</li>
    </ul>
    Copy the code

    React now knows that only elements with the ‘2014’ key are new; elements with the ‘2015’ and ‘2016’ keys are simply moved.

    In the real world, it is not difficult to generate a key. The element you want to present may already have a unique ID, so the key can be extracted directly from your data:

    <li key={item.id}>{item.name}</li>
    Copy the code

    When this is not the case, you can add an ID field to your model, or generate a key using a portion of the content as a hash. This key doesn’t need to be globally unique, but it needs to be unique in the list.

    Finally, you can also use the element’s index in the array as a key. This strategy is appropriate when elements are not being reordered, and diff will slow down if the order changes.

    Component state may encounter some problems when subscription-based components are reordered. Because component instances decide whether to update and reuse based on their key, if the key is a subscript, the order in which changes are made changes the current key, causing states of uncontrolled components (such as input fields) to tamper with each other and change unpredictably.

    Weigh the

    Keep in mind that the coordination algorithm is an implementation detail. React can re-render the entire application after each action, and the end result will be the same. In this context, rerendering means calling the Render method within all components, not that React will unload or load them. React will only decide how to merge differences based on the rules mentioned above.

    We regularly explore optimization algorithms to make common use cases perform more efficiently. In the current implementation, it is understood that a subtree can move between its siblings, but not anywhere else. In this case, the algorithm rerenders the entire subtree.

    React relies on exploratory algorithms, so performance suffers when the following assumptions are not met.

    1. The algorithm does not attempt to match subtrees of different component types. If you find yourself switching between two different types of components with very similar output, it is recommended to change them to the same type. In practice, we have encountered no such problems.
    2. KeyIt should be stable, predictable, and unique to the list. unstablekey(Such as throughMath.random()Generated) results in many component instances andDOMNodes are recreated unnecessarily, which can lead to performance degradation and loss of state in child components.

    Explain the DIff algorithm

    In the book Deep into the React Technology Stack, it is written that diff, as the accelerator of the Virtual DOM, has improved and optimized its algorithm, which is the basis and performance guarantee of the entire React interface rendering. It is also the most mysterious and incredible part of the React source code.

    React is best known for its combination of the Virtual DOM model and Diff, especially its efficient DIff algorithm, which allows users to refresh pages “freely” without worrying about performance issues and developers to not care about how Virtual DOM works. Because diff helps us figure out what really changes in the Virtual DOM and only do native DOM operations on that part of the Virtual DOM instead of re-rendering the entire page, it ensures an efficient rendering of the page after each update. Therefore, the Virtual DOM model and diff are the driving force behind React performance reputation. The diff algorithm was not invented. Just because the popularity of the algorithm is high, the efforts and contributions made by React to the optimization of diff algorithm should be recognized, which better reflects the charm and wisdom of React creators!

    Traditional DIff algorithm

    It is a complex and worthy problem to calculate the minimum operation of transforming one tree structure into another tree structure. The traditional DIFF algorithm compares nodes in turn through cyclic recursion, which is inefficient and the algorithm complexity reaches O(N3), where N is the total number of nodes in the tree. How scary is O(n3)? This means that if you want to show 1000 nodes, you have to perform a billion comparisons in turn. This exponential performance cost is too high for the front-end rendering scene. Today’s cpus can execute about 3 billion instructions per second, and even the most efficient implementations can’t calculate the difference in a second. Therefore, if React simply introduced the DIff algorithm without any optimization improvements, it would be far from efficient enough to meet the performance requirements of front-end rendering. If you want to introduce the idea of diff into the Virtual DOM, you need to design a stable and efficient diff algorithm. React does this! So how does diff work?

    Rounding out the diff

    React Converts the Virtual DOM tree into the actual DOM tree using a minimum of operations called reconciliation. Diff algorithm is a concrete implementation of harmonization. So how does this work?

    React converts O(N3) complexity problems into O(n) complexity problems by making bold strategies.

    O(n) is a completely linear, breadth-first hierarchical comparison, starting at the root node.

    1. The diff strategy

    The React Diff algorithm uses three strategies.

    • Strategy one:Web UIDOMThe movement of nodes across hierarchies is extremely rare and can be ignored.
    • Strategy 2: Two components with the same class will generate similar tree structures, and two components with different classes will generate different tree structures.
    • Strategy three: For a group of children at the same level, they can pass uniqueidMake a distinction.

    Based on the above strategies, React optimizes the tree Diff, Component Diff, and Element Diff algorithms. It turns out that these three prerequisite strategies are reasonable and accurate, which guarantee the performance of the overall interface construction.

    2. tree diff

    Based on strategy 1, React optimizes the algorithm of trees in a simple and straightforward way, that is, the trees are compared at different levels. Two trees will only compare nodes at the same level. React uses updateDepth to control the hierarchy of the Virtual DOM tree. Only DOM nodes at the same level are compared, that is, all child nodes under the same parent node. When a node is found to no longer exist, the node and its children are removed completely and are not used for further comparison. This allows you to compare the entire DOM tree with only one walk through the tree.

    // updateChildren: updateChildren: function(nextNestedChildrenElements, transaction, context) { updateDepth++; var errorThrown = true; try { this._updateChildren(nextNestedChildrenElements, transaction, context); errorThrown = false; } finally { updateDepth--; if (! updateDepth) { if (errorThrown) { clearQueue(); } else { processQueue(); }}}}Copy the code

    You might be wondering how diff would behave if DOM nodes were moved across hierarchies.

    As shown in the figure below, node A (including its children) is moved to node D entirely. React only takes into account the position change of nodes at the same level, but only creates and deletes nodes at different levels. When the root node finds that A has disappeared in the child node, it destroys A directly. When D discovers that A child node A is added, it creates A new child node (including the child node) as its child node. Diff :create A → create B → create C → delete A

    It can be seen that when A node is moved across the hierarchy, the imaginary movement operation does not occur, but the entire tree with node A as the root is recreated. This is an operation that affects React performance, so it is not recommended to operate across DOM nodes.

    Note that when developing components, maintaining a stable DOM structure can help improve performance. For example, you can hide or show nodes through CSS without actually removing or adding DOM nodes.

    3. component diff

    React builds applications based on components and uses a concise and efficient strategy for comparing components.

    • If the components are of the same type, continue the comparison according to the original policyVirtual DOMThe tree.
    • If not, the component is judged to bedirty componentTo replace all child nodes under the entire component.
    • For components of the same type, it is possible thatVirtual DOMNothing has changed, and if you know that for sure, you can save a lot of moneydiffComputation time. As a result,ReactAllow users to passshouldComponentUpdate()To determine whether the component needs to proceeddiffAlgorithm analysis.

    As shown below, when componentsDA component GEven if the two components are structurally similar, once Reactjudge DGIs a different type of component, does not compare the structure of the two, but deletes the component directlyDTo recreate the componentGAnd its sub-nodes. Although two components are of different types but similar in structure,diffIt affects performance, but just likeReactThe official blog says: There are few similarities between different types of componentsDOMTree, so it’s hard for this extreme factor to have a significant impact on the actual development process.

    4. element diff

    When nodes are at the same level, diff provides three node operations: INSERT_MARKUP, MOVE_EXISTING, and REMOVE_NODE.

    • INSERT_MARKUP: The new component type is not in the old collection, that is, the new node needs to be inserted into the new node.
    • MOVE_EXISTING: There is a new component type in the old collection and elementIs an updatable type,generateComponentChildrenHas been calledreceiveComponentIn this caseprevChild=nextChild, you need to do mobile exercises

    You can reuse the previous DOM node.

    • REMOVE_NODE: old component type, also present in the new collection, but correspondingelementIf not, it cannot be reused and updated directly and needs to be deleted, or if the old component is not in the new collection, it also needs to be deleted.

    As shown in the figure below, the old collection contains nodesA, B, C and D, the updated new collection contains nodesB, A, D and CAt this point the old and new sets proceeddiffDifferentiation contrast, discoveryB ! = AIs created and insertedBTo the new collection, delete the old collectionA; And so on, create and insertA, D and C, delete theB, C and D.

    React finds that such operations are cumbersome and redundant, because these nodes are the same, but their positions change, which requires complex and inefficient deletion and creation operations. In fact, you only need to move the positions of these nodes.

    React proposes an optimization strategy that allows developers to add unique keys to subnodes of the same hierarchy. Even though this is a small change, it makes a huge difference in performance.

    The nodes contained in the old and new sets are shown in the figure below. After diff differentiation comparison, it is found that the nodes in the new and old sets are all the same nodes through key. Therefore, node deletion and creation are not required, but the location of nodes in the old set is moved to the location of nodes in the new set. In this case, the React diff result is as follows :B and D do not perform any operations while A and C move.

    So how does such an efficient diff work?

    First, loop through for (name in nextChildren) the nodes in the new set, judge whether there is the same node in the new set if (prevChild === nextChild) by the unique key, if there is the same node, then move the operation. If (child._mountIndex < lastIndex), the position of the current node in the old set must be compared with that of lastIndex. Otherwise, this operation is not performed. This is a sequential optimization, and lastIndex is constantly updated to indicate the right-most (or largest) position of the visited node in the old set. If the currently accessed node in the new set is larger than lastIndex, it means that the currently accessed node is lower than the previous node in the old set, and the node does not affect the position of other nodes, so it is not added to the difference queue, that is, no move operation is performed. Only if the accessed node is smaller than lastIndex do you need to move it.

    In the figure below, the diFF differentiation comparison process is more clearly and intuitively described.

    • From the new collectionB, and then determine whether the same node exists in the old set B, a node is found BAnd then judge whether to move the node by comparing the node position.BPosition in the old setB._mountIndex = 1At this time,lastIndex = 0, does not meet the child._mountIndex < lastIndexTherefore, it is not trueBPerform the move operation. updatelastIndex = Math.max(prevChild._mountIndex, lastIndex), includingprevChild._ mountIndexsaidBPosition in the old set, thenlastIndex = 1And willBIs updated to the location in the new collectionprevChild._mountIndex = nextIndex, now in the new setB._mountIndex = 0.nextIndex++Enter the judgment of the next node.

    • From the new collectionA, and then determine whether the same node exists in the old setA, a node is foundAAnd then judge whether to move the node by comparing the node position.APosition in the old setA._mountIndex = 0When thelastIndex = 1To meet thechild._mountIndex < lastIndexThe condition, therefore, ofAMove operationenqueueMove(this, child._mountIndex, toIndex), includingtoIndexIn fact, isnextIndex, the table belowAPosition to move to. updatelastIndex = Math.max(prevChild._mountIndex, lastIndex),lastIndex = 1And willA Is updated to the location in the new collectionprevChild._mountIndex = nextIndex, now in the new set A._mountIndex = 1.nextIndex++Enter the judgment of the next node.
    • From the new collectionD, and then determine whether the same node exists in the old setD, a node is foundDAnd then judge whether to move the node by comparing the node position.DPosition in the old setD._mountIndex = 3, this

    When lastIndex = 1, the condition of child._mountIndex < lastIndex is not met, so D is not moved. Prevchild._mountindex = math.max (prevchild._mountIndex, lastIndex) Prevchild. _mountIndex = nextIndex in the new set, then d._mountIndex = 2 in the new set 6, nextIndex++ enter the judgment of the next node.

    • From the new collectionC, and then determine whether the same node exists in the old setC, a node is foundCAnd then judge whether to move the node by comparing the node position.CPosition in the old setC._mountIndex = 2At this time,lastIndex = 3To meet thechild._mountIndex < lastIndexThe condition, therefore, ofCMove operationenqueueMove(this, child._mountIndex, toIndex). updatelastIndex = Math.max(prevChild. 7 _mountIndex, lastIndex),lastIndex = 3And willCIs updated to the location in the new collectionprevChild._mountIndex = nextIndex, now in the new setA._mountIndex = 3.nextIndex++Enter the judgment of the next node. Due to theCIt’s already the last node, sodiffThis completes the operation.

    The above mainly analyzes the situation where there are the same nodes but different positions in the old and new sets, and the position of the nodes is moved. If the new collection has new nodes and the old collection has nodes that need to be deleted, how does the diff compare and contrast?

    • From the new collectionB, and then determine whether the same node exists in the old set B, you can find that nodes exist B. Due to theBPosition in the old setB._mountIndex = 1At this time,lastIndex = 0Therefore, noBPerform the move operation. updatelastIndex = 1And willBIs updated to the location in the new collectionB._mountIndex = 0.nextIndex++Enter the judgment of the next node.
    • From the new collectionE, and then determine whether the same node exists in the old setE, you can find that the node does not exist and create a new nodeE. updatelastIndex = 1And willEIs updated to the position in the new collection,nextIndex++Enter the judgment of the next node.
    • From the new collection C, and then determine whether the same node exists in the old set C, you can find that nodes existC. Due to theCPosition in the old setC._mountIndex = 2.lastIndex = 1At this time,C._mountIndex > lastIndexTherefore, noCPerform the move operation. updatelastIndex = 2And willCIs updated to the position in the new aggregation,nextIndex++Enter the judgment of the next node.
    • From the new collection A, and then determine whether the same node exists in the old setA, a node is foundA. Due to theAPosition in the old setA._mountIndex = 0.lastIndex = 2At this time,A._mountIndex < lastIndexAnd so onAPerform the move operation. updatelastIndex = 2And willAIs updated to the position in the new collection,nextIndex++Enter the judgment of the next node.
    • After the differential comparison of all nodes in the new set is completed, it is also necessary to cycle through the old set to determine whether there are nodes that are not in the new set but still exist in the old set. Such nodes are found at this timeDTherefore, the node is deleted DTo thisdiffThe operation is complete.

    Create, move, and delete nodes

    Of course, diff still has some shortcomings and needs to be optimized. As shown in the figure below, if the nodes of the new set are updated as D, A, B and C, only D node moves compared with the old set, while A, B and C still maintain the original order. Theoretically, DIff should only move D. However, since D is the largest in the old set, The _mountIndex of another node is less than lastIndex. As A result, A, B, and C move to the rear of node D instead of node D.

    The node of the new collection is updated toA, B, C, D

    During development, minimize operations like moving the last node to the head of the list. When the number of nodes is too large or update operations are too frequent, React rendering performance will be affected to some extent.

    conclusion

    1. diffThe complexity of the algorithm isO(n)Is a completely linear algorithm, breadth-first hierarchical comparison, starting at the root node.
    2. You can use indexesindexAs a keyValue, you can do this if it does not involve peer node movement, deletion, or modification, but it is not recommended because it may cause component state problems.
    3. diffThe policy involvesTree DIff, Component Diff, Element DiffThat we usekeyiselement diffRelated content.