Recently, I published my first small book, React Hooks vs. Immutable Data Streams, in Nuggets. The plan we made two months ago has been delayed until now, and we can often feel everyone’s anxiety in the Issue area of GitHub. I’m really sorry, but fortunately, it has finally come out successfully.

Within 5 days of launching, the sales volume has already exceeded 400 without any tweet introduction. I never expected this, but it also reflects the trust of digg friends in me. Probably look at the ID list in the background, and some are familiar faces, but it is almost no impression even complete strangers ID, indeed, look back in Denver on the growth of these days, the process of writing, thinking and struggle is very painful, but it is because you accidentally see articles, casual point praise, and give some feedback, Let me have enough fight and perseverance to carry on. We may have never met, or even have no wechat accounts with each other, but just in a place called “Digging Gold”, I received recognition from a stranger. This feeling has never been experienced before, which is also the motivation for me to keep on. Please let me sincerely say: Thank you very much!

Back to the booklet itself, there have been many partners to join the study. Nevertheless, I think I need to formally introduce this volume, because I feel that this is my responsibility as a volume author.

origin

The volume itself is a project tutorial, so why do I want to do such a project?

It’s kind of funny, I just wanted to make a nice project. I still remember the famous teacher of MOOC said in July that technology is actually very pure. In the end, there are only two things: one is to work to make money, and the other is to do what one wants to do. What I did later proved the latter. Most of the time, it is relatively easy and ordinary people can achieve 60 points, but to achieve 90 points or even higher, often need extraordinary hard work, and even need the right opportunity and talent. This is why there are a lot of similar projects online, I still insist on doing this project. I want to finish a project on my own, it must be delicate enough, and not for anyone.

I then tried to incorporate what I had learned over time, using React with Immutable data and using the react-sensitive hooks as the base stack for the project.

Why hooks?

I’d like to say that React Hooks are currently the “hot kids” in the front-end industry, because of their API simplicity and logic reuse. Vue3.0 uses a similar function-based model, so learning React Hooks is a big trend in the future. Here I also do not want to repeat the selling anxiety that it has been XXX years and if I do not learn XXX, I will be eliminated. In fact, there is no technology that must be learned. If it is good enough, I am willing to share it with you, so that more people can enjoy the convenience and efficiency improvement brought by it. As for Hooks, as someone who has used them in depth, I feel like I’m more than happy to share. Practicing and applying hooks on a specific project would be much better than trying to keep track of the documentation, and there would be some bugs in the process. Learning from the bug drive would deepen our understanding of the hooks principle.

Why Immutable data?

So that’s a little bit more complicated. I think I’d like to start with the React rendering mechanism — the Reconciliation process.

Apply colours to a drawing mechanism

React uses the virtual DOM (VDOM). Each time the props and state change, the render function returns a different tree of elements. React checks for the difference between the current tree and the last rendered tree. The Reconciliation process is based on the diff algorithm that compares the old and new DOM trees.

In order to achieve better performance, the first job is to reduce the diff process, so how to avoid this diff process on the premise of ensuring that the nodes that should be updated can be updated?

The answer is to declare a periodic function with shouldComponentUpdate. What does this function do?

The default shouldComponentUpdate returns true when the props and state change to indicate that the component is being rerendered, thus calling the Render function to diff the old and new DOM trees. However, we can make some judgments in this lifecycle function and return a Boolean value, true to update the current component, false to not update the current component. In other words, we can control whether the diff of the VDOM tree occurs via shouldComponentUpdate.

The key points have already been laid. Now let’s take an example from React to analyze the Reconciliation process:

The red nodes in the figure indicate that shouldComponentUpdate should return true and call the render method to diff the old and new VDOM tree. The green nodes indicate that this function returns false and no DOM tree updates are required.

If C1 is a red node, shouldComponentUpdate returns true, it is necessary to make a further comparison between the old and new VDOM trees, assuming that the C1 node type of the two trees is the same, then recursive into the next layer of node comparison, first enter C2, the green node, Indicates that SCU returns false and does not need to compare VDOM nodes of C2 and all descendant nodes below C2.

Now enter C3, which is the red node, indicating that SCU is true, and comparison needs to be carried out on this node. Assuming that the C3 nodes of the two trees are of the same type, the comparison will continue to enter the next layer. C6 in R is the red node, and the corresponding DIff operation is performed. C7 and C8 are green nodes, and they do not need to be updated.

Of course you might be wondering, all of the above are given the same node type in diff, but what happens when the node type is different? React removes the node and all descendants of the node from the original VDOM tree, and then replaces it with a node in the same position on the new VDOM tree. This belongs to the implementation details of the diff algorithm, and we will disassemble diff in a more comprehensive and detailed way in the Easter eggs at the end of this article 🙂

Therefore, we can find that if shouldComponentUpdate is properly used, unnecessary Reconciliation process can be avoided and application performance can be better.

Generally shouldComponentUpdate compares properties in props and state to determine whether to return true, which triggers the Reconciliation process. A typical application is the PureComponent API in React, which performs a superficial comparison between the props and state when they change.

But this project fully embraces functional components and no longer uses class components, so shouldComponentUpdate is no longer usable. After using functional components, is there no solution for shallow comparison? And it isn’t. React provides a memo method for function components. The only difference between this method and PureComponent is that only a superficial comparison is performed for functions because function components have no state. And it’s easy to use. Just pass the function into the Memo and export it. Like:

function Home () {
    //xxx
} 
export default memo (Home);
Copy the code

This explains why we need to wrap the Memo around each component export.

Now you have a set of optimizations.

Optimization plan 1: PureComponent (Memo) performs shallow comparison

In the last section I foreshadowed that the PureComponent or Memo would perform a shallow comparison between old and new data. You might be wondering, how does the shallow comparison work? I think it’s important to give you an intuitive feeling, so I’ll grab the core source code of the shallow PureComponent to give you a taste of it. Don’t be nervous. The logic is very simple.

function shallowEqual (objA: mixed, objB: mixed) :boolean {
  // The following is equivalent to === except for + 0 and -0, as well as NaN and NaN cases
  // Level 1: base data types are compared directly
  if (is (objA, objB)) {
    return true;
  }
  // Level 2: Return false as long as one is not an object data type
  if (
    typeofobjA ! = ='object' ||
    objA === null ||
    typeofobjB ! = ='object' ||
    objB === null
  ) {
    return false;
  }

  // We can make sure that both are object data types, and compare the number of attributes between them
  const keysA = Object.keys (objA);
  const keysB = Object.keys (objB);

  if(keysA.length ! == keysB.length) {return false;
  }

  // Check whether the attributes of the two are equal
  for (let i = 0; i < keysA.length; i++) {
    if(! hasOwnProperty.call (objB, keysA [i]) || ! is (objA [keysA [i]], objB [keysA [i]]) ) {return false; }}return true;
}
Copy the code

As YOU can see from the notes I wrote, four levels open up here, but it’s still a superficial comparison. In the following cases, judgment fails.

state: {a: ["1"]} -> state: {a: ["1"."2"]}
Copy the code

The a array has actually changed, but a shallow comparison would indicate that it hasn’t changed, because the array reference hasn’t changed. See? Shallow comparisons fail once the value of an attribute is of reference type.

This is the biggest disadvantage of this approach. Due to the JS reference assignment, this approach is only suitable for stateless components or components with very simple state data, for a large number of application components, it is powerless.

Optimization scheme two :shouldComponentUpdate for deep comparison

To solve the problem of solution 1, instead of doing superficial comparison, we recursively compare all properties and values in the props.

Let’s make some magic changes to the code above:

 function deepEqual (objA: mixed, objB: mixed) :boolean {
  // The following is equivalent to === except for + 0 and -0, as well as NaN and NaN cases
  // Level 1: Ensure that both are basic data types. The underlying data type directly compares the results.
  // The object type is different
  if (objA == null && objB == null) return true;
  if (typeofobjA ! = ='object' &&
      typeofobjB ! = ='object' &&
      is (objA, objB)) {
    return true;
  }
  // Level 2: Return false as long as one is not an object data type
  if (
    typeofobjA ! = ='object' ||
    objA === null ||
    typeofobjB ! = ='object' ||
    objB === null
  ) {
    return false;
  }

  // We can make sure that both are object data types, and compare the number of attributes between them
  const keysA = Object.keys (objA);
  const keysB = Object.keys (objB);

  if(keysA.length ! == keysB.length) {return false;
  }

  // Check whether the attributes of the two are equal
  for (let i = 0; i < keysA.length; i++) {
    if(! hasOwnProperty.call (objB, keysA [i]) || ! is (objA [keysA [i]], objB [keysA [i]]) ) {return false;
    } else {
        if(! deepEqual (objA [keysA [i]], objB [keysA [i]])){return false; }}}return true;
}
Copy the code

When the object’s attribute value is accessed, the attribute value is recursively compared, so as to achieve the effect of deep comparison. But think of an extreme case where we have 10,000 attributes, and only the last one changes, and we have to iterate through all 10,000 attributes. This is a huge waste of performance.

Solution 3: IMMUTABLE data structure + MEMO (SCU) shallow comparison

Getting back to the essence of the problem, we want to know whether the props (or state) data of the component has changed, either directly by shallow comparison or by deep comparison.

In this context, IMmutable data comes into being.

What is immutable data? What are its advantages?

Immutable data A persistent data structure created by sharing structures that, when part of a data structure is modified, returns a completely new object and shares the same nodes directly.

To be specific, immutable data internally uses the structure of a multi-fork tree, in that when a node is changed, it and all its associated parent nodes are updated.

Use a GIF to simulate the process:

Therefore, immutable is a great way to improve React rendering performance by updating data structures as efficiently as possible and communicating with existing PureComponent (Memo) to detect state changes.

Immutable is not a mutable object, but a mutable object is not a mutable object. It is not a mutable object, but a mutable object is not a mutable object.

The second is the controversy over the cost of learning the IMmutable API. I think it’s a matter of opinion, but my point is that if you’re currently stuck in an already well-used technology stack, and don’t bother to learn new apis, not to mention new technologies, I don’t think that’s a very good sign for personal growth.

Learning goals

Estimate a classmate to see above of return not satisfied, cross-examine a way :” learn to finish this have what use?” Here’s a look at the results and goals of this booklet:

  1. Be familiar with React Hooks for business development and understand which scenarios are generatedClosure trapHow to avoid potholes.
  2. Handwritten nearly6000 lines of codeAnd encapsulation13Basic UI components,12Fully master the whole process of React + Redux engineering coding.
  3. Encapsulate commonly used mobile components to achieve common requirements, such asEncapsulated rolling component,Image lazy loading,Implement pull up/pull down refreshThe function,To implement image stabilizationFunction,Implement component code segmentation(CodeSpliting), etc.
  4. Having practical project experience in implementing complex front-end interactions and improving your internal skills, such as developing a player kernel, is one of the big challenges.
  5. Master CSS in many skills, improve their CSS ability, regardless of layout or animation, have quite a lot of practice and exploration, did not use any UI framework, style code independent implementation.
  6. Thoroughly understand the principles of REdux and be able to independently develop redux middleware.

Small volume of outlook

After the release of the booklet, I also heard the feedback of digger friends, some of them corrected the article, also put forward suggestions to modify the project code, everyone’s active participation makes me dare not have a bit of slack off. The project is still being updated and maintained. In the future, some details of the project will be reconstructed according to the result of communication with everyone, and more Easter eggs will also be added. The current plan is to put the hooks source code analysis series into a small volume and add value to this volume continuously. We hope that you can give us a lot of support, and we also hope that you can get sufficient exercise and absorb enough experience through this project. More details about the project are not repeated here, and there has been a detailed introduction in the first section of the booklet.

Finally, I send you the address of the booklet, wish you a happy study!