React初学者使用useState的常见错误

285 阅读3分钟

引言

想法:上班摸鱼硬摸也难受,不如写点文章,那么写什么文章呢?

基础不牢地动山摇!!不如再熟悉总结一下基础,就从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,第一次 setCountcount 的值确实更新了,但此次执行的 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 对象,保持了状态的不可变性。

结语

这两个特性是我们日常学习和工作中一开始最容易犯的低级错误了,故在有时间的时候多总结归纳,避免再次犯这种低级错误。

感谢阅读~🌹🌹🌹