useState(initialState)
const [age, setAge] = useState(28);
const [name, setName] = useState('Taylor');
const [todos, setTodos] = useState(() => createTodos());
initialState可以接受任何类型的数据- 如果接受一个函数,那么这个函数需要保证是一个纯函数(pure)。这个函数不接受参数,同时它需要返回任何类型的数据,作为初始值
1. 不可局部更新
import {useState} from "react"
function App() {
const [user, setUser] = useState({
name: "John",
age: 20
})
const handleClick = () => {
setUser({
// 这里只更新age,会导致姓名消失
age: user.age + 1
})
}
return (
<>
<button onClick={handleClick}>addAge</button>
<div>{user.name}</div>
<div>{user.age}</div>
</>
)
}
export default App
上面的代码,当点击按钮之后,会导致 name 属性消失。其原因是React.useState的setState 并不会去帮你合并对象。正确的写法如下:
const handleClick = () => {
setUser({
...user, // 先copy 之前的对象
age: user.age + 1
})
}
2. 新的值和旧的值地址不相等
n !== n
import {useState} from "react"
function App() {
const [user, setUser] = useState({
name: "John",
age: 20
})
const handleClick = () => {
user.age = user.age + 1
setUser(user) // 新传入的user 和旧的 user相等,则不会触发更新
}
return (
<>
<button onClick={handleClick}>addAge</button>
<div>{user.name}</div>
<div>{user.age}</div>
</>
)
}
export default App
上面的代码代码在setUser的时候,传入的还是之前的user,不会触发更新。正确的做法是复制一份新的对象,并传入到setUser当中
const handleClick = () => {
const newUser = {
...user,
age: user.age +1
}
console(user === newUser) // false
setUser(newUser) // 新传入的user 和旧的 user相等,则不会触发更新
}
3. 在大部分情况下,都推荐在setState中传入函数
import {useState} from "react"
function App() {
const [user, setUser] = useState({
name: "John",
age: 20
})
const handleClick = () => {
setUser({
...user,
age: user.age + 1
})
setUser({
...user,
age: user.age + 1
})
// 点击按钮会年龄会增加2 吗?
}
return (
<>
<button onClick={handleClick}>addAge</button>
<div>{user.name}</div>
<div>{user.age}</div>
</>
)
}
export default App
如上代码,点击按钮的时候做了两次setState 的操作,那么年龄会增加 2 吗?答案是否定的。实际情况是,他永远都执行了第二个setUser。正确的做法是,在setState 里面传入一个函数:
const handleClick = () => {
setUser((a)=>({
...a,
age: a.age + 1
})
)
setUser((b)=>({
...b,
age: b.age + 1
})
)
}
setState(nextState)
setState 函数更新state为不同的值,并且触发一次重新渲染(re-render)
如果你传入一个函数作为nextState 。那么这个函数必须是纯函数(pure),关于纯函数,简单理解就是:函数接收什么就返回什么,想要深入理解去看下函数式编程,这也是React推崇的理念。
const add = (a,b) = > a+b // 加法函数,接受两个值并永远返回两个值的和
另外如果传入一个函数作为nextState,那么这个函数只接收一个参数,这个参数就是当前的state,可以理解为旧的值。并且这个函数需要返回 新的state 。React 会把你的更新函数放入一个队列当中。当下一次渲染来临时 React 会根据队列里面的更新函数计算出最新的state。
注意事项
1.setState 函数只会更新状态变量到下一次渲染。如果你在调用set函数后读取状态变量,你将仍然获取到调用之前屏幕上的旧值。如下代码两次打印 user.age 都将返回当前state中的值
const handleClick = () => {
setUser((a)=>({
...a,
age: a.age + 1
})
)
console.log(user.age) // 20
setUser((b)=>({
...b,
age: b.age + 1
})
)
console.log(user.age) // 20
}
- 如果提供的新的值和旧的值相同,那么会跳过重新渲染组件以及其子组件。在react中每次渲染都是从根组件往下渲染的,跳过相同值的渲染算是React的一种性能优化。
- React 会批处理状态更新。它会在所有事件处理程序运行并调用它们的
set函数后才更新屏幕。这样可以防止在单个事件期间进行多次重新渲染。在极少数情况下,如果你需要强制React提早更新屏幕,你可以使用flushSync。 - 在渲染过程中调用set函数只允许在当前正在渲染的组件内部进行。React将丢弃当前输出并立即尝试使用新状态重新渲染。这种模式很少需要使用,但你可以用它来存储来自先前渲染的信息
// App.js
import { useState } from 'react';
import CountLabel from './CountLabel.js';
export default function App() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
<CountLabel count={count} />
</>
);
}
//CountLabel.js
import { useState } from 'react';
export default function CountLabel({ count }) {
const [prevCount, setPrevCount] = useState(count);
const [trend, setTrend] = useState(null);
if (prevCount !== count) {
setPrevCount(count);
setTrend(count > prevCount ? 'increasing' : 'decreasing');
}
return (
<>
<h1>{count}</h1>
{trend && <p>The count is {trend}</p>}
</>
);
}
- 在严格模式下,为了帮助你发现意外的不纯性,React会调用更新函数两次。这只是开发过程中的行为,不会影响生产环境。如果你的更新函数是纯函数,这不会影响行为。其中一次调用的结果将被忽略。
import ReactDOM from 'react-dom/client'
import React from 'react'
import App from './App.jsx'
ReactDOM.createRoot(document.getElementById('root')).render(
// 严格模式
<React.StrictMode>
<App />
</React.StrictMode>
)
更多useState的用法去React官网进行查看 useState – React
完。