A,useStatePrinciple and source code

useStateThe running process of

function App() { const [n, setN] = React.useState(0); Const [user, setUser] = react. useState({name:'F'}); // Const [user, setUser] = react. useState({name:'F'}); return ( <div className="App"> <p>{n}</p> <p> <button onClick={() => setN(n + 1)}>+1</button> </p> </div> ); } ReactDOM.render(<App />, rootElement);Copy the code

Operation process:

  • For the first time to renderrender<App/>
  • callApp()Get the virtual Div object, and then create the real Div to the page.
  • Call when the user clicks a buttonsetN(n+1)Once again,render<App/>
  • React compares two divs and DOM Diff updates DIV locally.

In this process, useState is run every time the App function is called. Will the result of useState(0) be different each time the App is re-executed?

function App() { const [n, setN] = React.useState(0); console.log(`n:${n}`) return ( <div className="App"> <p>{n}</p> <p> <button onClick={() => setN(n + 1)}>+1</button> </p>  </div> ); }Copy the code

A few questions:

  • What happens when setN is executed?
    • A: Rerender the UI (because n+1)
  • Does n change?
    • A: No. It’s changing n, not already changing n, which means setN doesn’t change n
  • Will App() be re-executed?
    • A: will
  • If App() will be reexecuted, thenuseState(0)Is the value of n different each time?
    • A: Yes, through log, each time click the button, n+1, the execution of App(), each time the log out n value is different. Why does the same line of code have different results when executed several times?

Analysis of the

  • setNI have to modify some data x, put n plus 1 into x
  • setNMust trigger<App/>To re-render
  • useStateIt must read the latest value of n from x
  • Each data has its own data x, which we’ll name state

UseState source code for the primary idea

import React from "react"; import ReactDOM from "react-dom"; const rootElement = document.getElementById("root"); let _state; function myUseState(initialValue) { _state = _state === undefined ? initialValue : _state; function setState(newState) { _state = newState; render(); } return [_state, setState]; Const render = () => reactdom.render (<App />, rootElement); function App() { const [n, setN] = myUseState(0); return ( <div className="App"> <p>{n}</p> <p> <button onClick={() => setN(n + 1)}>+1</button> </p> </div> ); } ReactDOM.render(<App />, rootElement);Copy the code

Implement two or more Usestates

What if a component uses two Usestates?

  • Because all the data is in_state, so there will be conflicts

Improvement ideas

  • Try to make_stateMake it an object, like_state= {n:0, m :0}. But in doing so,useState(0)I don’t know if it’s n:0 or m:0
  • Try to make_stateMake it an array, for example_state = [0, 0]This method seems feasible

The code examples

import React from "react"; import ReactDOM from "react-dom"; const rootElement = document.getElementById("root"); let _state = []; let index = 0; function myUseState(initialValue) { const currentIndex = index; index += 1; _state[currentIndex] = _state[currentIndex] || initialValue; const setState = newState => { _state[currentIndex] = newState; render(); }; return [_state[currentIndex], setState]; Const render = () => {index = 0; <App />, rootElement); // Reactdom. render(<App />, rootElement); }; function App() { const [n, setN] = myUseState(0); const [m, setM] = myUseState(0); console.log(_state); return ( <div className="App"> <p>{n}</p> <p> <button onClick={() => setN(n + 1)}>+1</button> </p> <p>{m}</p> <p> <button onClick={() => setM(m + 1)}>+1</button> </p> </div> ); } ReactDOM.render(<App />, rootElement);Copy the code

The principle of useState is similar to the code I implemented above. There is a state array that holds all the data in a function component. These data are distinguished by subscript index. Each data has its own index in order when useState() is called. The n returned by this call is used to read the value.

Clicking on the button calls setN(n+1), which assigns the new value to the corresponding item in the state array, and then renders it again. As soon as it renders, App() will be called, and useState will be executed again. We continue to use the last value, which is the updated value. This ensures that when rendering multiple times, the same line of useState(0) is executed, the result will be up to date.

Each component has its own state and index, stored in its own virtual DOM. If the button is clicked and setState() is executed, it changes state and triggers an update, it renders again, App() is called again, and useState is executed again, which reads state[index], the most recent value, and then generates a new virtual DOM

_stateDisadvantages of the array scheme:

  • UseState call sequence. If the first render is n is the first, M is the second, and K is the third. The second rendering is required to ensure that the order is consistent. You can’t use if… Else out of order.
  • The App used_stateindexWhat are the other components?
    • Solution: Create one for each component_stateindex
  • _stateindexWhat if I put the same name in the global scope?
    • Solution: Place it on the virtual node object corresponding to the component

summary

  • Each function component corresponds to a React node, called FiberNode
  • Each node stores state and index, state is memorizedState, index implementation uses linked list structure
  • useStateWill readstate[index]
  • The index byuseState Order of callsdecision
  • setStateChanges state and triggers an update

UseRef and useContext

setN(n+1)It doesn’t change n

import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

function App() {
  const [n, setN] = React.useState(0);
  const log = () => setTimeout(() => console.log(`n: ${n}`), 3000);
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>+1</button>
        <button onClick={log}>log</button>
      </p>
    </div>
  );
}

ReactDOM.render(<App />, rootElement);
Copy the code

There is a problem with the above code

  • There is no bug with +1 and log
  • Click log and then click +1, there is a bug, why log prints the last data

Puzzle: Because there are multiple n’s

That’s because setN doesn’t change n. +1 first, n will be 1 when rendering, the original 0 will be recycled automatically. And then the log, it prints out a 1. But if you log first, you print n three seconds later. +1, in the second render App, n is 1, but the first render n=0 is still there, the n is not changed, so the print is still 0

Because any change to the data triggers a UI update, App() must be called again. So for example, the first time you call App, it’s the first render, n is 0. If you add n+1, you call App a second time, it’s the second render, and in this new App, n is 1. But the n in the original App is still 0, it hasn’t changed. It just generates another new piece of data.

So that means that whenever I change the data, render it once, I’m going to generate a copy of N.

What do YOU do if you want to have a persistent state

  • Use global variables, such aswindow.xxx
  • useuseRefUseRef can be used not only for divs, but also for arbitrary data. However, useRef does not automatically trigger updates when attributes change. You can only set updates manually, which is not recommended
  • With useContext, you can use it not only throughout, but also across different components

useRef

import React from "react"; import ReactDOM from "react-dom"; const rootElement = document.getElementById("root"); function App() { const nRef = React.useRef(0); const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000); Return (<div className="App"> <p>{ref.current}) </p> <p> <button onClick={() => (ref.current +=) 1)}>+1</button> <button onClick={log}>log</button> </p> </div> ); } ReactDOM.render(<App />, rootElement);Copy the code

Don’t use useState, because n generated by useState will be executed once to produce one copy. Use useRef. The data is stored in useref. current and there is only one copy of this. Change the useref. current value to +1, so log first, then +1, the printed value is the new n. Because no new useref.current is generated, it remains the same object no matter how many times it is updated

Bug: UI does not update automatically, press +1 and the n on the page does not change. Although the value of useref. current has actually changed, it will not be updated to the UI.

useContext

import React from "react"; import ReactDOM from "react-dom"; import "./styles.css"; const rootElement = document.getElementById("root"); const themeContext = React.createContext(null); function App() { const [theme, setTheme] = React.useState("red"); return ( <themeContext.Provider value={{ theme, setTheme }}> <div className={`App ${theme}`}> <p>{theme}</p> <div> <ChildA /> </div> <div> <ChildB /> </div> </div> </ themecontext.provider >// can be understood as a scope); } function ChildA() { const { setTheme } = React.useContext(themeContext); return ( <div> <button onClick={() => setTheme("red")}>red</button> </div> ); } function ChildB() { const { setTheme } = React.useContext(themeContext); return ( <div> <button onClick={() => setTheme("blue")}>blue</button> </div> ); } ReactDOM.render(<App />, rootElement);Copy the code

It can run through not only one component, but other components as well. It allows all descendant components in the specified scope to access the uppermost component’s read-write data interface

summary

  • Each time you re-render, the function component is executed
  • All states corresponding to the function component are copied again
  • If you don’t want duplicated states, you can use useRef, or useContext

UseState Precautions

1. Cannot be locally updated

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

function App() {
  const [user,setUser] = useState({name:'Frank', age: 18})
  const onClick = ()=>{
    setUser({
      name: 'Jack'
    })
  }
  return (
    <div className="App">
      <h1>{user.name}</h1>
      <h2>{user.age}</h2>
      <button onClick={onClick}>Click</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Copy the code

If state is an object, there are multiple properties in it. In setUser, if only one of the attributes is modified, the other will become undefined. Because setState does not automatically merge properties.

Should use… Syntax, copy all the properties of this object first, and then set the properties you want to change.

const onClick = ()=>{ setUser({ ... Age :20})}Copy the code

2. Change your address

const onClick = ()=>{
    user.age=30
    setUser(user)
}
Copy the code

For example, if I want to change user.age, I will set user.age to a new value. You can see that when YOU click on the button, the UI doesn’t update.

React does not update the UI when it detects that the user address has not changed.

So to give a new address, just pass an object to set, so it’s not the same address.

const onClick = ()=>{ setUser({ ... user name: 'Jack' }) }Copy the code

3. UseState can accept functions

const [user,setUser] = useState(
    () => ({name:'Frank', age: 18})
 )
Copy the code

Returns an initial value as if it had been passed in directly. The advantage of accepting a function is that if the calculation of the initial value is complicated, the function form will only be performed once, so it will only be evaluated on the first time. If you’re passing the initial value directly, you’re going to evaluate it every time you come in. (But we usually pass the initial value directly)

Advantages: This function returns the initial value state and is executed once. Reduce unnecessary computation

4. SetState can also accept functions

Usage scenario: I want to add n+1 first, then n+2. If I write it like this, the expected effect is that when I click the button, n on the page is 3

import React, {useState} from "react"; import ReactDOM from "react-dom"; function App() { const [n, SetN] = useState(0) const onClick = ()=>{setN(n+1) setN(n+2) // setN(I => I +1)} return ()  <div className="App"> <h1>n: {n}</h1> <button onClick={onClick}>+3</button> </div> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);Copy the code

But when I click the button, N becomes 1.

Because we said before, setN of n plus 1 doesn’t change n, it generates a new n. That is, if I do setN(n+1) on the first row, then the n is still 0, and then if I do setN(n+2) on this n, which means I add n to 2, which means I add 0 to 2, then I get 2. No matter how many rows setN has, it’s only executing the last row.

If you want to operate continuously on a state, you can use a function

const onClick = ()=>{
     setN(i=>i+1)
     setN(i=>i+2)
}
Copy the code

There’s no n in setN. There’s only one function, and that function represents one operation, plus one operation, plus two operation. It doesn’t say add n to 1. It’s just a placeholder I for an operation.

React (n=1); React (n=1); React (n=1);

Function writing is more recommended.

5. Do not change the location of useState or use the if statement

const[n,setN]=React.useState(0)
const[user,setUser]=React.useState({name:"varown"})
Copy the code

Do not reverse or change their positions. Keep them the same as when they were first written

6. The initial value of useState is valid only for the first time

Here’s an example:

When I click the button to change the value of name, I find that in the Child component, it is received, but the value is not assigned to name through useState!

Code demo

const Child = memo(({data}) =>{ console.log('child render... ', data) const [name, setName] = useState(data) return ( <div> <div>child</div> <div>{name} --- {data}</div> </div> ); }) const Hook =()=>{ console.log('Hook render... ') const [count, setCount] = useState(0) const [name, setName] = useState('rose') return( <div> <div> {count} </div> <button onClick={()=>setCount(count+1)}>update count </button> <button onClick={()=>setName('jack')}>update name </button> <Child data={name}/> </div> ) }Copy the code