A,useState
Principle and source code
useState
The 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 render
render<App/>
- call
App()
Get the virtual Div object, and then create the real Div to the page. - Call when the user clicks a button
setN(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, then
useState(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
setN
I have to modify some data x, put n plus 1 into xsetN
Must trigger<App/>
To re-renderuseState
It 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
_state
Make 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
_state
Make 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
_state
Disadvantages 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
_state
和index
What are the other components?- Solution: Create one for each component
_state
和index
- Solution: Create one for each component
_state
和index
What 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
useState
Will readstate[index]
- The index by
useState
Order of callsdecision setState
Changes 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 as
window.xxx
- use
useRef
UseRef 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