React-hooks: React-hooks: React-hooks: React-hooks: React-hooks: React-hooks: React-hooks
Abstract
When using the Hook feature to write components, always feel its simplicity and convenience. Of course, “there’s no such thing as a free lunch”, it sacrifices readability and risks memory leaks (mentioned at the end). But that doesn’t stop you from exploring its magic.
Hopefully, you’ve read the Hook documentation or used it in your project before you begin. But if you are only interested in functional programming, you can also gain something.
To make the writing flow more smoothly, I’m going to throw out a few questions that will be addressed as the source code is implemented:
- 🤔️ Implementation principles of useState
- 🤔️ why can’t hooks be used inside loops and judgments
- 🤔️ Implementation principle of useEffect
- 🤔️ useEffect application scenarios
- 🤔 ️
Class
vsHooks
⚠ ️ code are implemented by the TypeScript, were in all the demos gist.github.com/dongyuanxin…
The implementation principle of useState
When useState is called, a meta-ancestor of the form (variable, function) is returned. And the initial value of state is the argument passed in when useState is called externally.
Now that we’ve sorted out the parameters and return values, let’s take a look at what useState does. As shown in the code below, when the button is clicked, setNum is executed, the state num is updated, and the UI view is updated. Obviously, the function useState returns to change the state automatically calls the Render method to trigger the view update.
function App() { const [num, setNum] = useState < number > 0; Return (< div > < div > num: {num} < / div > < button onClick = {() = > setNum (num + 1)} > 1 < / button > < / div >). }Copy the code
Given the above exploration, with the help of closures, encapsulate a setState as follows:
function render() { ReactDOM.render(<App />, document.getElementById("root")); } let state: any; function useState<T>(initialState: T): [T, (newState: T) => void] { state = state || initialState; function setState(newState: T) { state = newState; render(); } return [state, setState]; } render(); // First renderCopy the code
This is a simple use of the prototype of useState. It also solves the problem of “how 🤔️ useState works” mentioned at the beginning of the article. But if the function declared in multiple state, in the current code, only the first state is to take effect (see state = the state | | initialState;) ).
Why not use hooks inside loops and judgments
Don’t worry about the question. The idea goes back to how useState supports multiple states. React Hooks: Not Magic, Just Arrays. React Hook looks like a magic implementation, but is essentially implemented via Array.
In the previous simple implementation of useState, the initial state was stored in a global variable. By analogy, multiple states should be kept in a dedicated global container. This container is just an unpretentious Array object. The specific process is as follows:
- On the first rendering, state by state is declared and placed into the global Array in useState order. Each time you declare state, increase the cursor by one.
- Update state to trigger rendering again. The cursor is reset to 0. The view is updated by fetching the latest state values in the order in which useState is declared.
Take a look at the diagram below. Each time you use useState, a new STATE is added to the STATE container.
The code is as follows:
import React from "react"; import ReactDOM from "react-dom"; const states: any[] = []; let cursor: number = 0; function useState<T>(initialState: T): [T, (newState: T) => void] { const currenCursor = cursor; states[currenCursor] = states[currenCursor] || initialState; Function setState(newState: T) {states[currenCursor] = newState; render(); } ++cursor; // update: cursor return [states[currenCursor], setState]; } function App() { const [num, setNum] = useState < number > 0; const [num2, setNum2] = useState < number > 1; return ( <div> <div>num: {num} < / div > < div > < button onClick = {() = > setNum (num + 1)} > 1 < / button > < button onClick = {() = > setNum (num 1)} > 1</button> </div> <hr /> <div>num2: <button onClick={() => setNum2(num2 * 2)}> </button onClick={() => setNum2(num2 /) </button> </div> </div> } function render() { ReactDOM.render(<App />, document.getElementById("root")); cursor = 0; // reset cursor} render(); // First renderCopy the code
At this point, if you want to use hooks where loops, judgments, etc., are not at the top of function components, look like this:
let tag = true;
function App() {
const [num, setNum] = useState < number > 0;
// Only the first render is executed
if (tag) {
const [unusedNum] = useState < number > 1;
tag = false;
}
const [num2, setNum2] = useState < number > 2;
return (
<div>
<div>num: {num}</div>
<div>
<button onClick={()= >SetNum (num + 1)}> add 1</button>
<button onClick={()= >SetNum (num - 1)}> minus 1</button>
</div>
<hr />
<div>num2: {num2}</div>
<div>
<button onClick={()= >SetNum2 (num2 * 2)}> Double</button>
<button onClick={()= >SetNum2 (num2/2)}> Shrink to double</button>
</div>
</div>
);
}
Copy the code
Since tag=false is reset in the conditional logic, subsequent renderings do not enter the conditional statement. Doesn’t look like a problem? However, since useState is implemented based on Array+Cursor, the mapping between state and Cursor is shown in the following table during the first rendering:
The variable name | cursor |
---|---|
num | 0 |
unusedNum | 1 |
num2 | 2 |
When the click event triggers the rendering again, it does not enter useState in the conditional judgment. So, when CURSOR =2, the corresponding variable is num2. Num2 corresponds to cursor 3. SetNum2 doesn’t work.
Here, we solve the question “why can’t 🤔️ use Hook in loop and judgment”. When using a Hook, use it at the top of a function component!
Implementation principle of useEffect
When exploring the principle of useEffect, I have been troubled by a question: What is the effect and use of useEffect? Of course, the side effects of functions are something anyone can say. Here’s an example:
function App() {
const [num, setNum] = useState(0);
useEffect((a)= > {
// Simulate an asynchronous request for back-end data
setTimeout((a)= > {
setNum(num + 1);
}, 1000); } []);return <div>{! num ? "Request backend data..." ${num} '}</div>;
}
Copy the code
This code, although organized in a more readable way, can be interpreted as a side effect of the function. But this is not necessary. It is perfectly possible to use setTimeout without useEffect and update the state of the function component in its callback function.
Only after reading A Complete Guide to useEffect and building your own Hooks did I understand why useEffect is necessary and meaningful.
In the second argument to useEffect, we can specify an array that will not trigger this side effect if the elements in the array are unchanged the next time we render it (analogous to the life cycle of the Class Class for Nextprops and prevProps). The benefit is obvious, useEffect can avoid unnecessary render as needed, rather than writing it directly on the top layer of a function component.
Here is a TypeScript implementation of useEffect that does not include side effect destruction:
Const allDeps: any[][] = []; let effectCursor: number = 0; function useEffect(callback: () => void, deps: any[]) { if (! AllDeps [effectCursor]) {// First render: assign + call callback allDeps[effectCursor] = deps; ++effectCursor; callback(); return; } const currenEffectCursor = effectCursor; const rawDeps = allDeps[currenEffectCursor]; Const isChanged = rawdeps.some ((dep: any, index: number) => dep! == deps[index] ); if (isChanged) { callback(); allDeps[effectCursor] = deps; // thank you juejin@carlzzz for correcting} ++effectCursor; } function render() { ReactDOM.render(<App />, document.getElementById("root")); effectCursor = 0; // Set effectCursor to 0}Copy the code
The useEffect implementation is easier to understand with the example below. Of course, you can also make an asynchronous request in this useEffect and call the state update function after receiving the data without bursting the stack.
function App() {
const [num, setNum] = useState < number > 0;
const [num2] = useState < number > 1;
// Multiple triggers
// Every time the button is clicked, the setNum function is triggered
// The side effect detects a change in num and calls the callback function automatically
useEffect((a)= > {
console.log("num update: ", num);
}, [num]);
// Only the first time
// Only fires once on compoentDidMount
// The side effect function is not executed more than once
useEffect((a)= > {
console.log("num2 update: ", num2);
}, [num2]);
return (
<div>
<div>num: {num}</div>
<div>
<button onClick={()= >SetNum (num + 1)}> add 1</button>
<button onClick={()= >SetNum (num - 1)}> minus 1</button>
</div>
</div>
);
}
Copy the code
⚠️ useEffect The first callback can return a function to destroy side effects, equivalent to the unmount life cycle of a Class component. This is not implemented for the sake of illustration.
In this section, we try to answer two questions: “implementation principle of 🤔️ useEffect” and “application scenario of 🤔️ useEffect”.
Class VS Hooks
Hooks seem cooler and simpler though. But in real development I prefer to use Class to declare components. The comparison of the two methods is as follows:
Class | Hooks |
---|---|
Clean code logic (constructors, componentDidMount, etc.) | Variable names and comments are required |
Less prone to memory leaks | Prone to memory leaks |
In general, Hooks are more demanding to write code, and in the absence of an effective mechanism to keep code readable and safe, Class is still my first choice. Here is an example of a memory leak (there is no way to circumvent this practice of passing status update functions globally) :
import React, { useState } from "react";
import ReactDOM from "react-dom";
let func: any;
setInterval((a)= > {
typeof func === "function" && func(Date.now());
console.log("interval");
}, 1000);
function App() {
const [num, setNum] = useState < number > 0;
if (typeoffunc ! = ="function") {
func = setNum;
}
return <div>{num}</div>;
}
function render() {
ReactDOM.render(<App />, document.getElementById("root"));
}
render();
Copy the code
Refer to the link
- React hooks: not magic, just arrays
- A Complete Guide to useEffect
- UseEffect complete guide
- The React principle of Hooks:
useEffect
The callback function calls the update function of state, which will burst the stack
There are many improper opinions in the article, welcome to discuss and correct.