useState
useState是React的一个Hook函数,它可以在函数式组件中添加一个状态变量。
使用方式为
const [state, setState] = useState(initialState)
传入的参数是一个状态的初始值initialState
返回值是一个数组,数组中第一个值是当前的state,在首次渲染的时候为initialState。第二个值是用于更新当前state值的函数setState,并触发重新渲染。
注意事项:
useState
是一个 Hook,因此你只能在 组件的顶层 或自己的 Hook 中调用它。
在组件中的使用方式为
import { useState } from 'react';
function App() {
const [count, setCount] = useState(0)
const [msg, setMsg] = useState('msg')
}
set函数
set函数有两种使用方式
-
直接给state赋予新的值
-
传入一个可以根据先前的state来按照一定的逻辑更新state的函数
setState(state => { return state + 1 })
注意事项:
-
set函数只会在下一次渲染的时候更新状态变量state的值,也就是说如果在set函数之后读取状态变量state的值,那么只会读取到本次渲染的值,也就是未进行更新的值。
// count = 1 setCount(count + 1) console.log(count) // 1
-
如果你提供的新值与当前
state
相同(由Object.is
比较确定),React 将 跳过重新渲染该组件及其子组件。- 对于基本数据类型,
Object.is
可以直接判断出来二者是否相同。 - 对于引用数据类型,
Object.is
是根据引用地址进行判断的。
- 对于基本数据类型,
-
React 会 批量处理状态更新。它会在所有 事件处理函数运行 并调用其 set 函数后更新屏幕。这可以防止在单个事件期间多次重新渲染。也就是说React会把多次setState的更新合并到同一个。
多次更新state,效果却一样
// UseStateDemo.jsx
import { useState } from "react"
const UseStateDemo = () => {
const [count, setCount] = useState(0)
const [msg, setMsg] = useState('msg')
const handleClick = () => {
console.log('count', count)
setCount(count + 1)
console.log('first add', count)
setCount(count + 1)
console.log('second add', count)
}
return (
<div>
<div>{msg}-count: {count}</div>
<div>
<button onClick={handleClick}>add</button>
</div>
</div>
)
}
export default UseStateDemo
会发现react把两次的更新合并到一起,所以每一次执行setCount的时候,组件内部的count其实并没有及时更新,所以count的值还为0。在第二次执行setCount的时候,也是在执行 count + 1 = 0 + 1 = 1。所以最终渲染出来的count为1
为了解决这个问题,我们就需要react执行批量按顺序更新,也就是setState需要根据上一次state的值来得到最新的state的值。所以就需要用到set函数的第二个用法。
import { useState } from "react"
const UseStateDemo = () => {
...
const handleClick = () => {
console.log('count', count)
// setCount(count + 1)
// console.log('first add', count)
// setCount(count + 1)
// console.log('second add', count)
setCount(count => {
console.log('first add', count)
return count + 1
})
setCount(count => {
console.log('second add', count)
return count + 1
})
console.log('final-count', count)
}
return (
...
)
}
export default UseStateDemo
实际上,采取第二种方式的时候,React会将更新的操作放在一个队列里面,然后,在下一次渲染期间,React将按照相同的顺序调用它们,并在事件处理函数的结尾去依次清空队列传入上一个值。
- 执行
count => count + 1
,接收count = 0
为初始状态,返回count = 1
为下一个状态; - 执行
count => count + 1
,接收count = 1
为初始状态,返回count = 2
为下一个状态;
已经更新状态,但是日志仍然显示旧值
import { useState } from "react"
const UseStateDemo = () => {
const [count, setCount] = useState(0)
const [msg, setMsg] = useState('msg')
const handleClick = () => {
console.log('count', count)
setCount(count + 1); // 请求使用 1 重新渲染
console.log(count); // 仍然是 0!
let timer = setTimeout(() => {
clearTimeout(timer)
console.log('setTimeout', count); // 还是 0!
}, 5000);
}
return (
<div>
<div>{msg}-count: {count}</div>
<div>
<button onClick={handleClick}>add</button>
</div>
</div>
)
}
export default UseStateDemo
这是因为state表现如同一张快照,当触发setState的时候,就会触发一次渲染,而每一次渲染函数内部都拥有自己独立的 props 和 state。在上面代码中,触发setCount进入到第二张快照。但是这不影响已经运行的事件处理函数中的 count
状态变量。这个时候setTimeout拿到的 count
状态变量其实是第一张快照的值,也就是 0 。
为了解决这个问题,可以用一个新的变量来保存 count
状态变量更新后的值,并用这个新变量来执行后面的操作,再触发SetCount。
const nextCount = count + 1;
setCount(nextCount);
console.log(count); // 0
console.log(nextCount); // 1