State
跟Vue的data相似, 但又有不同.State是一个快照, 对应于组件某一时刻的状态, 修改State的变量不会更改已经存在的state变量, 但是会出发重新渲染.
为何要使用State
- state能保存渲染间的数据
- state的修改能触发更新
以下示例1中, 点击Next按钮预期是可以累加, 但是却不能所愿.原因是点击Next按钮并没有触发更新, index变量的累加值没有渲染到页面上.
function Gallery() {
let index = 0;
function handleClick() {
index = index + 1;
}
return (
<>
<button onClick={handleClick}>
Next
</button>
{index}
</>
);
}
以下示例2就可以实现以上例子的功能
import { useState } from 'react';
export default function Gallery() {
const [index, setIndex] = useState(0);
function handleClick() {
setIndex(index + 1);
}
return (
<>
<button onClick={handleClick}>
Next
</button>
{index}
</>
);
}
State是一个快照
设置它不会更改你已有的 state 变量,但会触发重新渲染。
下面的示例3不能完成预期的功能, 预期点击+3按钮, 会累加3, 但是实际情况是累加1
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>
</>
)
}
原因如下:
- State是一个快照
- React会对State的更新做批处理,当批处理完成后UI才会更新
- setNumber(number + 1);连续运行3次, 又因为在此次渲染中, number的快照值是0, 所以批处理实际执行的代码为如下代码, 最终number被更新为了1
setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);
如何在下次渲染前多次更新同一个State呢
答案是设置器中不再传入特定的值, 而是传入一个函数, 函数接收React传入的当前快照的State更新队列的上一个state变量值
下面示例4可以完成示例3的功能
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
}}>+3</button>
</>
)
}
下面是示例5可以进一步验证, 最后运行结果为42
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 5);
setNumber(n => n + 1);
setNumber(42);
}}>增加数字</button>
</>
)
}
State中更新对象和数组
更新不建议直接修改源对象和数组, 而是应该更新源对象和数组的副本, 然后将副本赋值给源对象和数组.
可以使用Immer库完成快速的更新
示例6, 使用了对象的展开符号创建对象副本, 最终副本赋值给源persion
import { useState } from 'react';
export default function Form() {
const [person, setPerson] = useState({
firstName: 'Barbara',
lastName: 'Hepworth',
email: 'bhepworth@sculpture.com'
});
function handleFirstNameChange(e) {
setPerson({
...person,
firstName: e.target.value
});
}
return (
<>
<label>
First name:
<input
value={person.firstName}
onChange={handleFirstNameChange}
/>
</label>
<p>
{person.firstName}{' '}
{person.lastName}{' '}
({person.email})
</p>
</>
);
}
示例7, 使用Immer, 更加简洁了
import { useImmer } from 'use-immer';
export default function Form() {
const [person, updatePerson] = useImmer({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
image: 'https://i.imgur.com/Sd1AgUOm.jpg',
}
});
function handleNameChange(e) {
updatePerson(draft => {
draft.name = e.target.value;
});
}
return (
<>
<label>
Name:
<input
value={person.name}
onChange={handleNameChange}
/>
</label>
<p>
<i>{person.artwork.title}</i>
{' by '}
{person.name}
<br />
(located in {person.artwork.city})
</p>
<img
src={person.artwork.image}
alt={person.artwork.title}
/>
</>
);
}