引言
想法:上班摸鱼硬摸也难受,不如写点文章,那么写什么文章呢?
基础不牢地动山摇!!不如再熟悉总结一下基础,就从useState开始!
useState的更新是异步的 🧊
useState的更新是异步的,且在同一个作用域内多次调用useState的dispatch
函数(通常命名为setXxx
)不会立即触发重新渲染。
❌ 错误用法
下面是一个代码示例:
import React, { useState } from 'react';
const Component = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
// 开发者期望计数增加两次
console.log('当前计数:', count); // 打印 1
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>handleClick</button>
</div>
);
};
export default Component;
在这个例子中,开发者意图是将计数增加两次。然而,由于状态更新的异步性质,两次 setCount 调用都是基于相同的初始状态,结果只增加了一次计数。
⏳ 原因
React 官方文档提到:组件内部的任何函数,包括事件处理函数和 Effect,都是从它被创建的那次渲染中被「看到」的,所以引用的值任然是旧的,最后导致 setState 出现异常。
上面代码,Component 组件实际也是个闭包函数,handleClick 里面引用着 count,第一次 setCount 后 count 的值确实更新了,但此次执行的 handleClick 事件处理函数作用域还是旧的,里面引用的 count 仍然为旧的(依旧是useState里的初始值),导致第二次 setCount 后结果为 1
相当于每次setCount操作的都是useState里的初始值
如果对闭包还不是很了解的同学可以看一下我的这篇文章juejin.cn/post/716389…
✅ 正确用法
import React, { useState } from 'react';
const Component = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
// 现在每次更新都正确地依赖于最新状态
};
// 可选:使用 useEffect 来查看更新后的状态
useEffect(() => {
console.log(count); // 2
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>handleClick</button>
</div>
);
};
export default Component;
在上述代码中,每次调用 setCount 都使用状态的最新值,确保了准确和顺序的更新。
⭐ 进阶
结合校招面百度的一道代码题:
下面代码的表现?如何改为1s后count+1 ?
const App = () => {
const [count, setCount] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1)
console.log('当前定时器计数:', count);
}, 1000)
return () => clearInterval(timer)
}, [])
return <div>{count}</div>
}
export default App;
- 下面代码表现?
页面一直显示
1
,log日志一直显示0
(原因还是闭包)
- 如何改为1s后count+1?
setCount(count + 1)
改成setCount(count => count + 1)
dispatch(setXxx) 的参数为一个函数时, 该函数的参数,是上一次返回最新的 state,返回值作为新的 state。
忽略状态的不可变性 🧊
❌ 错误用法
import React, { useState } from 'react';
const ProfileComponent = () => {
const [profile, setProfile] = useState({ name: 'John', age: 30 });
const updateAge = () => {
profile.age = 31; // 直接改变状态
setProfile(profile);
};
return (
<div>
<p>Name: {profile.name}</p>
<p>Age: {profile.age}</p>
<button onClick={updateAge}>Update Age</button>
</div>
);
};
export default ProfileComponent;
这段代码错误地直接改变了 profile 对象。这样的改变不会触发重新渲染,并导致不可预测的行为。
⏳ 原因
在上述代码中,由于profile内存地址没有变化,所以没有监听到变化,setProfile没有触发重新渲染
在 React 中,状态应该被视为不可变的。一个常见的错误是直接改变状态,特别是对于像对象和数组这样的复杂数据结构。
state 中可以存储任意类型的 js 值。对于基础数据类型,可以通过直接替换它们的值来触发一次渲染。对于对象或数组,则需要创建一个新的对象或数组并将其传递给 state 的设置函数。
✅ 正确用法
import React, { useState } from 'react';
const ProfileComponent = () => {
const [profile, setProfile] = useState({ name: 'John', age: 30 });
const updateAge = () => {
setProfile({...profile, age: 31}); // 正确更新状态
};
return (
<div>
<p>Name: {profile.name}</p>
<p>Age: {profile.age}</p>
<button onClick={updateAge}>Update Age</button>
</div>
);
};
export default ProfileComponent;
在修改后的中,updateAge 使用扩展运算符创建了一个具有更新后年龄的新 profile 对象,保持了状态的不可变性。
结语
这两个特性是我们日常学习和工作中一开始最容易犯的低级错误了,故在有时间的时候多总结归纳,避免再次犯这种低级错误。
感谢阅读~🌹🌹🌹