preface

The benefits of React hooks are not covered here, check out the documentation. The main focus of this article is to explain in detail the use of the various hooks and to explain some simple hooks implementations to help us understand hooks. The first and most important of these hooks is useState.

useState

The use of useState

The biggest benefit of Hooks is that they allow you to use state and other React features without writing a class. UseState lets you useState in functional components. Let’s take a look at the specific use:

import React, { useState } from "react";
import ReactDom from "react-dom";

function Counter() {
  let [count, setCount] = useState(0); / / define the state: the count
  return (
    <>
      <p>{count}</p>
      <button onClick={()= > setCount(count + 1)}>+</button>
    </>
  );
}

function render() {
  ReactDom.render(<Counter />.document.getElementById("root"));
}

render();
Copy the code

In the above code, the Counter component is a functional component that passes in an initial value through useState and then returns count and setCount. Count changes each time the component is called. SetCount is used to change the value of count, and each change triggers a rerendering of the count component. From the above analysis, we can see that useState mainly has the following functions:

  1. Accepts a parameter as the initialization value
  2. Returns an array whose first value is the latest state count and whose second value is a function that modifies the state setCount
  3. Need to trigger rerender after setCount is set

Preliminary implementation of useState

Based on the functions of useState analyzed above, we preliminarily implement a simple useState.

let state;
function useState(initialState) {
  state = state || initialState;
  function setState(newState) {
    state = newState;
    render();
  }
  return [state, setState];
}
Copy the code

The implementation idea is as follows:

  • A state is declared externally to receive the initial and updated values. Why define it outside the useState function? If defined in a function, this value is set to its initial value every time it is re-rendered, then the latest value will not be retrieved.
  • Internally define a function that updates state and triggers rerendering. The function takes a newState, assigns it to an external state, and then calls the Render function to rerender the component.
  • Returns an array. In the array is the latest state and the method to update the state.

Let’s replace useState with React’s useState and see how it works.

import React from "react";
import ReactDom from "react-dom";

// Custom useState
let state;
function useState(initialState) {
  state = state || initialState;
  function setState(newState) {
    state = newState;
    render();
  }
  return [state, setState];
}

Components / / Counter
function Counter() {
  let [count, setCount] = useState(0);
  return (
    <>
      <p>{count}</p>
      <button onClick={()= > setCount(count + 1)}>+</button>
    </>
  );
}

function render() {
  ReactDom.render(<Counter />.document.getElementById("root"));
}
render();
Copy the code

The above code does some of the functionality of the original useState, but there are a few problems: what if there are multiple UsEstates? What if YOU save multiple states? Let’s look at the following code, okay?

function Counter() {
  let [count, setCount] = useState(0);
  let [num, setNum] = useState(0); // Share the same state. Modifying the second will overwrite the first.
  return (
    <>
      <p>{num}</p>
      <button onClick={()= > setNum(num + 1)}>+</button>
      <p>{count}</p>
      <button onClick={()= > setCount(count + 1)}>+</button>
    </>
  );
}
Copy the code

Since all of our data shares the same state, modifying one will cause the other to be overwritten. To solve this problem, we must provide a variable for each data to store state and avoid collisions. The solution is to use an array for saving.

let state = []; // The state array is used to store data
let index = 0; // index is used for each array item
function useState(initialState) {
  let currentIndex = index; // currentIndex stores the currentIndex
  state[currentIndex] = state[currentIndex] || initialState;
  function setState(newState) {
    state[currentIndex] = newState;
    render();
  }
  index += 1; // Index is increased by 1 after each change
  return [state[currentIndex], setState];
}

function render() {
  index = 0; // render needs to restore index
  ReactDom.render(<Counter />.document.getElementById("root"));
}
Copy the code

The implementation idea is as follows:

  • willstateDeclared as an array, each data corresponds to some item of the array
  • Declare an indexindex, each data corresponds to an index value
  • setStateSet values by manipulating indexes
  • Per calluseStateWe need to set index+=1. This ensures that multiple data sets have different index values
  • The returned value is also obtained by index
  • Every timerenderSet index to 0 to ensure that each index is the same each time the render component is rerendered, all the useState in the component is executed once, and each index is assigned again, so set index to 0 each time. Make sure the indexes are consistent each time. That is why hooks cannot be written in if,while, etc.

The core point above is to ensure that each useState data corresponds to the same index. In other words:

  • For the first rendering, the index value of count is 0 and the index value of num is 1.
  • On the second rendering, the index value of count is still 0 and the index value of num is 1.
  • On the third rendering, the index value of count is still 0 and the index value of num is 1.
  • .

We define multiple data sets, use useState multiple times, and observe the index value of each data set:

import React from "react";
import ReactDom from "react-dom";

let state = [];
let index = 0;
function useState(initialState) {
  console.log("index", index); / / observation
  let currentIndex = index;
  state[currentIndex] = state[currentIndex] || initialState;
  function setState(newState) {
    state[currentIndex] = newState;
    render();
  }
  index += 1;
  return [state[currentIndex], setState];
}

function Counter() {
  let [count, setCount] = useState(0); // The first useState index index
  let [num, setNum] = useState(0); // The second useState index index
  let [count1, setCount1] = useState(0); // The third useState index index
  let [num1, setNum1] = useState(0); // The fourth useState index index
  return (
    <>
      <p>{num}</p>
      <button onClick={()= > setNum(num + 1)}>+</button>
      <p>{count}</p>
      <button onClick={()= > setCount(count + 1)}>+</button>
    </>
  );
}

function render() {
  index = 0;
  ReactDom.render(<Counter />.document.getElementById("root"));
}

render();
Copy the code

We check the final print order as follows:

index 0
index 1
index 2
index 3
Copy the code

In any case, these four indexes are always 0, 1, 2, and 3. The following is not allowed.

function Counter() {
  let [count, setCount] = useState(0);
  // Define useState in conditional statements
  if (count % 2= =0) {
    let [count1, setCount1] = useState(0);
  }
  if (num % 2= =0) {
    let [num1, setNum1] = useState(0);
  }
  let [num, setNum] = useState(0);

  return (
    <>
      <p>{num}</p>
      <button onClick={()= > setNum(num + 1)}>+</button>
      <p>{count}</p>
      <button onClick={()= > setCount(count + 1)}>+</button>
    </>
  );
}
Copy the code

If we define useState in a conditional statement, we might have only two usEstates at the first time, with indexes 0 and 1 for count and num. But the next time you have four usestates, the indexes for count and num will be 0 and 3. In this case, the index value of num changes, and it gets a different value from the array in different cases, not its own value, which can cause an error. Therefore, if uesState is defined in a conditional statement, the following error occurs:

React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render
Copy the code

conclusion

So far, we’ve introduced

  • The use of useState
  • Preliminary implementation of useState
  • UseState implementation process facing problems, and solutions

Through the above introduction, we can deepen our understanding of useState. Of course, this is not the official implementation method, but a simplified way to understand. The purpose is just to help us make better use of useState.