【 Module 2 】 Get to the bottom of the core principles

React-hooks: Behind ‘principles’,’ Principles’

The React team has two react-hooks guidelines for developers, which are as follows:

  • Only call Hook in React function;

  • Do not call hooks in loops, conditions, or nested functions.

React-hooks: React-hooks: React-hooks: React-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks: react-hooks As for principle 2, I believe many people have stumbled on it, or are still dubious about it. In fact, all the “don ‘ts” emphasized in principle 2 point to the same goal, which is to ensure that Hooks execute in the same order every time they render.

Why is order so important? This starts with the Hooks implementation mechanism. Next, take useState as an example to explore the working principle of react-hooks in depth.

[Note] : This Demo is based on the React 16.8.x version.

What is the problem if you do not ensure that Hooks are executed in order?

Let’s start with a small Demo:

import React, { useState } from "react"; Function PersonalInfoComponent() {// Define variables let name, age, career, setName, setCareer; [name, setName] = useState(" fix "); [age] = useState("99"); // Get career status [career, setCareer] = useState(" I am a front-end, love to eat bear cookies "); Console. log("career", career); Return (<div className="personalInfo"> <p> Name: {name}</p> <p> Age: {age}</p> <p> Profession: {career} < / p > < button onClick = {() = > {elegantly-named setName (" show shes "); </button> </div>); } export default PersonalInfoComponent;Copy the code

The PersonalInfoComponent renders an interface that looks like this:

Here’s the picture.

The PersonalInfoComponent displays personal information, including name, age, and occupation. For testing purposes, The PersonalInfoComponent also allows you to click the “Modify Name” button to modify the name information. After clicking once, “Su-yan” will be changed to “Su-yan”, as shown below:

So far, the components are behaving as expected, and everything seems to be in perfect harmony. But if I made a small change to the code and put a portion of the useState operation in the if statement, things would be very different. The modified code is as follows:

import React, { useState } from "react"; IsMounted = false; // isMounted = false; Function PersonalInfoComponent() {// Define variable logic unchanged let name, age, career, setName, setCareer; // This is a debug operation console.log("isMounted is", isMounted); // Append if logic here: only when rendering for the first time (the component is not yet mounted) will the name and age states be fetched if (! IsMounted) {// eslint-disable-next-line [name, setName] = useState("修 修 "); // eslint-disable-next-line [name, setName] = useState("修 修 "); // eslint-disable-next-line [age] = useState("99"); // Set isMounted to true after the internal logic is executed once. } // The logic for career information remains unchanged [career, setCareer] = useState(" I am a front-end, love to eat bear biscuits "); // Append the output to career, which is also a debug operation console.log("career", career); Return (<div className="personalInfo"> {name? <p> name: {name}</p> : null} {age? < p > age: {age} < / p > : null} < p > career: {career} < / p > < button onClick = {() = > {elegantly-named setName (" show shes "); </button> </div>); }Copy the code

export default PersonalInfoComponent; The initial rendering of the modified component will have the same interface as the previous version:

Notice that when you imitate this code on your own computer, Don’t leave out the // eslint-disable-next-line comment in the if statement — most React projects now have built-in strong validation for react-rex-rules. Putting Hooks in the if statement in the sample code is a non-compliance operation, which is directly recognized as an Error and causes the program to report an Error. In this case, only by disabling the esLint validation for the relevant code can we avoid the error nature, so that we can more intuitively see what the effect of the error is and understand the cause of the error.

When the modified component is initially mounted, the actual logical content is the same as that of the previous version, which involves obtaining and rendering the three states of name, age and career. In theory, the change should happen when the second render is triggered by clicking “Change name” : isMounted is set to true, and the logic inside if is skipped. At this point, according to the design intention given in the code comments, I hope to obtain and display only the state of Career during the second rendering. So will things go according to plan? Here’s what happens when you click the Change Name button:

Not only did the component not change its interface as expected, it even reported an error. The error message reminds us that this is because “there are fewer Hooks in component rendering than expected”.

Indeed, according to the existing logic, useState is called three times in the initial rendering and only once in the second rendering. But just because of that, should I report an error?

Logically speaking, there should be no problem with rendering as long as the career value obtained during the second rendering (because the second rendering actually only renders the career state), and React has no reason to block the rendering action. So is there something wrong with Career? To see what happens, we will try to output isMounted and CAREER each time we render.

First, reset the interface back to the initial mounted state and observe the output of the console, as shown below:

The key isMounted and Career variables are circled in red: isMounted is false, indicating the initial rendering; A career value of “I’m a front-end and love bear cookies” is also fine.

Next, click the “Change Name” button and take a look at the contents of the two variables, as shown below:

IsMounted is true for secondary rendering. However, career was changed to Soo-yeon. How weird is that? That’s not what it says in the code. Check the callback content of the button click event as follows:

<button onClick={() => {setName(); }} > </button>Copy the code

Indeed, the code is correct, setName is called here, so it should also change the state to name, not career.

Then why did career change in the end? Let’s take a look at the Hooks implementation mechanism.

Hooks rely on sequential lists for proper operation

The emphasis on “source code flow” rather than “source code” is based on two main considerations:

  • React-hooks are closely related to Fiber on the source level. We are still in the stage of laying the foundation, so there is no discussion on the underlying implementation of Fiber mechanism. Blindly reading source code is meaningless at this stage.

  • The principle! == the source code, reading the source code is just a means to understand the principle, in some cases, reading the source code can help us quickly locate the essence of the problem (for example, the react. createElement source code can help us quickly understand what JSX is converted to); However, the react-hooks source link is relatively long, and there is a lot of “dirty logic” in the key function renderWithHooks. Generally speaking, learning cost is high, and learning effect is difficult to guarantee.

To sum up, here will not be detailed to post each line of specific source code, but for key methods to do a focus on analysis. It is also not advisable to deal with Hooks without understanding the underlying implementation of Fiber. The key to understanding why Hooks must execute in the same order is not to understand what each line of code does, but to understand what the entire call link looks like. It is easy to understand how Hooks work if you can understand what they do at each of the key points, and how they affect the final render result.

3. Use useState as an example to analyze the call link of react-hooks

First of all, react-hooks call links are different in “first render” and “update” phases. Here we have summarized the links in the two phases separately.

  • First, take a look at the first rendering process, as shown below:

In this process, a series of operations triggered by useState eventually fall into the mountState, so it’s important to focus on what the mountState does. MountState source code:

Function mountState(initialState) {var hook = mountWorkInProgressHook(); If (typeof initialState === 'function') {// $FlowFixMe: Flow doesn't like mixed types initialState = initialState(); Const queue = hook. Queue = {last: null, dispatch: null, lastRenderedReducer: basicStateReducer, lastRenderedState: (initialState: any), }; // Save initialState as a "memory". MemoizedState = hook.baseState = initialState; // Dispatch is created by a method in the context called dispatchAction, Var dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue); // Dispatch returns the target array. Dispatch is the setXXX function that we often see in examples. return [hook.memoizedState, dispatch]; }Copy the code

As you can see from this source, the main job of mounState is to initialize the Hooks. The mountWorkInProgressHook method is the most important part of the source code, which tells us the organization of the data structure behind the Hooks. MountWorkInProgressHook method

MemoizedState: null, memoizedState: null, memoizedState: null null, baseQueue: null, queue: null, next: null }; If (workInProgressHook === null) {// Each React version does the same thing: FirstWorkInProgressHook = workInProgressHook = hook; } else {workInProgressHook = workInProgreshook. next = hook; } // return the current hook return workInProgressHook; }Copy the code

It can be seen from here that all information related to hook converges into a hook object, and hook objects are connected in series in the form of “one-way linked list”.

  • Here’s a bigger picture of the update process:

As you can see from the highlighted part of the figure, the difference between a first render and an update render is whether you call mountState or updateState. We’ve been very clear about what mountState does; Although there is a lot of code involved in the operation link after updateState, it is actually easy to understand what it does: it iterates through the previously built linked list in sequence and extracts the corresponding data information for rendering.

Now put mountState and updateState together: mountState (first render) builds a linked list and renders; UpdateState iterates through and renders the linked list in turn.

At this point, you probably get a sense of what’s going on? Yes, hooks render by “walking through” each hooks content. If the list is read differently from one another, the result of rendering will be out of control.

This phenomenon is a bit like building an array of definite length, where each pit corresponds to an exact piece of information, and each subsequent value from the array can only be located by index (i.e. location). It is for this reason that in many articles, Hooks are essentially arrays. As you will learn in this article, Hooks are essentially linked lists.

Restore this known result to the PersonalInfoComponent to see how variables change in the actual project.

4. Recreate the execution of the PersonalInfoComponent from the underlying perspective

Let’s review the modified PersonalInfoComponent code:

import React, { useState } from "react"; IsMounted = false; // isMounted = false; Function PersonalInfoComponent() {// Define variable logic unchanged let name, age, career, setName, setCareer; // This is a debug operation console.log("isMounted is", isMounted); // Append if logic here: only when rendering for the first time (the component is not yet mounted) will the name and age states be fetched if (! IsMounted) {// eslint-disable-next-line [name, setName] = useState("修 修 "); // eslint-disable-next-line [name, setName] = useState("修 修 "); // eslint-disable-next-line [age] = useState("99"); // Set isMounted to true after the internal logic is executed once. } // The logic for career information remains unchanged [career, setCareer] = useState(" I am a front-end, love to eat bear biscuits "); // Append the output to career, which is also a debug operation console.log("career", career); Return (<div className="personalInfo"> {name? <p> name: {name}</p> : null} {age? < p > age: {age} < / p > : null} < p > career: {career} < / p > < button onClick = {() = > {elegantly-named setName (" show shes "); </button> </div>); } export default PersonalInfoComponent;Copy the code

There are three useState calls we can extract from the code:

[name, setName] = useState(" "); [age] = useState("99"); [career, setCareer] = useState(" I am a front-end and love to eat bear biscuits ");Copy the code

All three calls occur during the first rendering, and the associated linked list structure is shown below:

When the first rendering ends and the second rendering takes place, only one useState call actually occurs:

UseState (" I'm a front-end and love to eat bear cookies ")Copy the code

At this time, the linked list is as shown in the figure below:

So let’s go over what happens when we update (secondary render) : updateState iterates through the list, reads the data, and renders. Note that this process is exactly the same as evaluating an array in order (or index). So React doesn’t look at whether you named the variable career or something else, it just recognizes your useState call, so it inevitably thinks: Oh, you want the first hook.

It should then have the following effect:

In this way, career naturally takes the value of “xiu yan” in the hook object of the head node of the linked list.

5, summary

This is the end of the react-hooks study. For react-hooks, we looked at “motivation”, we looked at “working modes”, and finally we dug deep into the underlying principles of react-hooks using source code. All of this was done to really understand react-hooks, not only in practice, but in interviews.

Next, we will enter the real “deep water area” of the whole column, and gradually enter the knowledge link of “virtual DOM → Diff algorithm → Fiber architecture”. In the following study, we will continue and strengthen the style of “inquiring at the bottom” and challenge the core part of React by sticking closely to the source code, principles and questions. The real battle has just begun. Come on ~

Learning the source (the article reprinted from) : kaiwu.lagou.com/course/cour…