设置 state 只会为下一次渲染变更 state 的值
一个 state 变量的值永远不会在一次渲染的内部发生变化
React 会使 state 的值始终"固定"在一次渲染的各个事件处理函数内部
React 会等到事件处理函数中的所有代码都运行完毕再处理 state 更新
用 flushSync 可以同步更新 state
- 在一个函数中,多次设置state, 最终结果由最后一次决定
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
// 第一次执行时,number为0
setNumber(number + 1); // 相当于setNumber(0 + 1),React 准备在下一次渲染时将 number 更改为 1,React 将 “替换为 1” 添加到其队列中。
setNumber(number + 1); // 相当于setNumber(0 + 1),React 准备在下一次渲染时将 number 更改为 1,React 将 “替换为 1” 添加到其队列中。
setNumber(number + 1); // 相当于setNumber(0 + 1),React 准备在下一次渲染时将 number 更改为 1,React 将 “替换为 1” 添加到其队列中。
// 执行结果 number = 1
}}>+3</button>
</>
)
}
- 即使事件处理函数的代码是异步的,它获取到的state也是与本次最初执行时的值保持一致的,state在一次执行时不会改变。
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 5);
setTimeout(() => {
alert(number); // 0 在onClick执行时,number的值就被固定了。state 在“获取 UI 的快照”时就被“固定”了
}, 3000);
}}>+5</button>
</>
)
}
如何在重新渲染前,多次更新同一个state
setNumber(n => n + 1)
通过传入一个更新函数 n=>n+1
来更新state的值
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(n => n + 1); // n => n + 1 是一个函数。React 将它加入队列。
setNumber(n => n + 1); // n => n + 1 是一个函数。React 将它加入队列。
setNumber(n => n + 1); // n => n + 1 是一个函数。React 将它加入队列。
}}>+3</button>
</>
)
}
当在下次渲染期间调用 useState 时,React 会遍历队列。 之前的 state 的值是 0,所以这就是 React 作为参数 n 传递给第一个更新函数的值。然后 React 会获取上一个更新函数的返回值,并将其作为 n 传递给下一个更新函数,以此类推:
更新队列 | n | 返回值 |
---|---|---|
n => n + 1 | 0 | 0 + 1 = 1 |
n => n + 1 | 1 | 1 + 1 = 2 |
n => n + 1 | 2 | 2 + 1 = 3 |
React 会保存 3 为最终结果并从 useState 中返回。
用 flushSync 同步更新 state
flushSync ,强制 React 同步更新(“刷新”)DOM ,类似与 $nextTick 。
// 自动滚至新添加的元素位置
// 当 flushSync 中的代码执行后,立即同步更新 DOM。因此,滚动到最后一个待办事项时,它已经在 DOM 中了
import { useState, useRef } from 'react';
import { flushSync } from 'react-dom';
let nextId = 0;
export default function TodoList() {
const listRef = useRef(null);
const [text, setText] = useState('');
const [todos, setTodos] = useState([]);
function handleAdd() {
const newTodo = { id: nextId++, text: text };
flushSync(() => {
setText('');
setTodos([ ...todos, newTodo]);
});
listRef.current.lastChild.scrollIntoView({
behavior: 'smooth',
block: 'nearest'
});
}
return (
<>
<button onClick={handleAdd}>添加</button>
<input
value={text}
onChange={e => setText(e.target.value)}
/>
<ul ref={listRef}>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</>
);
}