🚀 Vue 开发者看过来!一文吃透 React 的 useState

作为 Vue 转 React 的前端,我刚开始接触 useState 时踩了不少坑。这篇文章我会用尽量通俗的方式,帮你彻底掌握它的用法和原理!

什么是 useState?

React 官方对 useState 的定义是:

"useState 是一个 Hook,它允许你在函数组件中添加状态。"

这意味着,在 React 函数组件中,你可以像类组件一样维护局部状态。

我的理解

useState 相当于 Vue 组件里的 data,但它的更新方式和思路和 Vue 不一样,得靠 setState 显式更新。


useState 的基本用法

我们简单举个栗子,一个常见的计数器

ts 复制代码
import { useState } from 'react'
​
export default function Counter() {
    const [count, setCount] = useState(0)
​
    return (
        <button onClick={() => setCount(count + 1)}>
            点击了 {count} 次
        </button>
    )
}

当我们点击按钮时,会触发 setCount 方法,改变 count 的值。

React 会存储新状态,使用新值重新渲染组件,并更新 UI。

useState 返回一个由两个值组成的数组,例如 [count, setCount]

  • 第一个参数是当前的 state,初始值为 useState(initialState) 传入的值, initialState 可以是任何类型的值。
  • 第二个参数是 set 方法,它允许你把 state 改变为任何其他值。你可以随意命名,但最好统一命名为:setInitialState.

需要注意的一点是:useState 状态的值更新时,组件会重新渲染。

举个栗子

ts 复制代码
import { useState } from "react";
​
export default function Counter() {
  const [count, setCount] = useState(0);
​
  console.log("执行Counter");
​
  return <button onClick={() => setCount(count + 1)}>点击了 {count} 次</button>;
}
​

可以看到我们每次触发 count 改变都会重新执行一次 Counter 函数,如果想避免这种情况也有办法,那就是 React 的另外一个 HookuseMemo,本文暂不做讲解,感兴趣的自行前往官网查阅

多个 useState 的执行顺序很重要

ts 复制代码
function Demo() {
  const [count, setCount] = useState(0);
  const [visible, setVisible] = useState(true);
​
  // 注意顺序不能动态变化!
}

❌ 错误示例

ts 复制代码
if (someCondition) {
  const [flag, setFlag] = useState(false); // 报错:Hook 调用顺序改变
}

✅ 解决思路

  • 始终把所有的 Hook 放在组件顶层,不要放在 if/for/函数里。

React 内部依靠 Hook 的「调用顺序」来管理每个 state,如果调用顺序发生变化,React 无法正确"对应"上每个 state。

这也是 React 为了性能和可预测性作出的权衡,不使用依赖名称,而是通过顺序索引定位 state,需要我们在编码中遵守「稳定顺序」这一原则。

接下来我们看一些常见的 useState 使用陷阱,提前避坑少踩雷!

🧨 陷阱1:state 如同一张快照

举个栗子

ts 复制代码
import { useState } from 'react'
​
export default function Counter() {
    const [count, setCount] = useState(0)
​
    const handleClick = () => {
        setCount(count + 1)
        console.log('点击了按钮, 计数器的值为: ' + count);
    }
​
    return (
        <button onClick={handleClick}>
            点击了 {count} 次
        </button>
    )
}

点击一次按钮,控制台会输出什么?

是不是感觉很奇怪,为什么 count 的值还是改变前的值?

难道 count 的改变是异步的?我们加个定时器看看:

ts 复制代码
import { useState } from 'react'
​
export default function Counter() {
    const [count, setCount] = useState(0)
​
    const handleClick = () => {
        setCount(count + 1)
        setTimeout(() => {
            console.log('点击了按钮, 计数器的值为: ' + count);
        }, 2000);
    }
​
    return (
        <button onClick={handleClick}>
            点击了 {count} 次
        </button>
    )
}

上面栗子得出结论,state 更新的值跟异步无关,我们套用官网的解释

一个 state 变量的值永远不会在一次渲染的内部发生变化, 即使其事件处理函数的代码是异步的。

React 会使 state 的值始终"固定"在一次渲染的各个事件处理函数内部。

那如果我们就是想在重新渲染之前读取最新的 state 怎么办?

解决方案:set 传入更新函数

在说明解决方案前我们再举一个栗子:多次更新数据

ts 复制代码
export default function Counter() {
    const [count, setCount] = useState(0)
​
    const handleClick = () => {
        setCount(count + 1)
        setCount(count + 1)
        setCount(count + 1)
    }
​
    return (
        <>
            <h1>{count}</h1>
            <button onClick={handleClick}>增加数字</button>
        </>
    )
}

大伙们觉得点击一次按钮,会输出什么结果?

调用了3次 setCount(count + 1),结果会是3吗?

正如前面所说的,每一次渲染的state值都是固定的 ,因此无论你调用多少次 setCount(count + 1),在第一次渲染的事件处理函数内部的 count 值总是 0

我们可以通过传入一个 更新函数 去解决此类问题,比如 setCount(count=>count + 1),我们改写上面栗子

ts 复制代码
export default function Counter() {
    const [count, setCount] = useState(0)
​
    const handleClick = () => {
        setCount(count=>count + 1)
        setCount(count=>count + 1)
        setCount(count=>count + 1)
    }
​
    return (
        <>
            <h1>{count}</h1>
            <button onClick={handleClick}>增加数字</button>
        </>
    )
}

现在尝试下点击按钮看看效果

栗子中的 count=>count + 1 称为更新函数,当你传递给 set 方法作为参数时:

  • React 会将此函数加入队列,以便在事件处理函数中的所有其他代码运行后进行处理。
  • 在下一次渲染期间,React 会遍历队列并给你更新之后的最终 state。

🧠 为什么 React 不像 Vue 那样,直接修改值就能响应?

React 的理念是数据驱动视图的单向数据流 ,通过 setState 显式调用,能:

  • 更清晰知道 state 的更新来源,方便调试和测试;
  • 允许 React 批量处理多次 state 更新,提高性能;
  • 与函数式编程理念一致,避免隐式副作用;

相比之下,Vue 的响应式更自动化,但也容易陷入"修改值却视图不更新"的问题,React 的方式更「显性」。

🧨 陷阱2:state 中的对象、数组更新

举个栗子

ts 复制代码
export default function User() {
    const [user, setUser] = useState(
        {
            name: 'JiangJiang',
            age: 18 
        }
    )

    const handleClick = () => {
        user.age = 19
    }

    return (
        <>
            <h1>名字:{user.name}</h1>
            <h2>年龄:{user.age}</h2>
            <button onClick={handleClick}>长大</button>
        </>
    )
}

这里我们不通过 setUser 函数,而是直接去操控 user 对象,去改变其 age 值。

不出意外,果然是不行的,让我们看看官方文档是怎么描述的:

state 中可以保存任意类型的 JavaScript 值,包括对象。但是,你不应该直接修改存放在 React state 中的对象。相反,当你想要更新一个对象时,你需要创建一个新的对象(或者将其拷贝一份),然后将 state 更新为此对象。

可以得出结论,每次去改变对象的值时,必须通过 set 方法去进行改变,并且相当于重新创建一个对象(或者是浅拷贝),然后将 state 更新为此对象。

ts 复制代码
export default function User() {
    const [user, setUser] = useState({
        name: 'JiangJiang',
        age: 18
    })

    const handleClick = () => {
        setUser({
            name: 'JiangJiang',
            age: user.age - 1
        })
    }

    return (
        <>
            <h1>名字:{user.name}</h1>
            <h2>年龄:{user.age}</h2>
            <button onClick={handleClick}>逆龄</button>
        </>
    )
}

嘿嘿嘿...

更新数组的方式也一样:

ts 复制代码
const [list, setList] = useState([1, 2, 3]);

setList([...list, 4]); // 添加元素
setList(list.filter(item => item !== 2)); // 删除元素

React 默认是通过「浅比较」来判断 state 是否变化的(也就是 === 判断)。

  • 直接修改对象属性不会改变对象引用,React 判断"没变"就不会更新;
  • 而创建新对象,改变了引用,React 才能识别到变化。

useState 和 Vue 响应式的差异

对比项 React (useState) Vue (响应式)
状态存储 函数组件内通过 useState 定义 组件实例中的 data
状态更新 必须用 setState 直接修改属性即可
原理 状态更新会触发整组件重新执行 依赖收集 + 精准更新视图
易错点 不能直接修改 state 响应式陷阱较多(如数组变异方法)

✅ 总结

  • useState 是 React 函数组件管理状态的基础
  • 用法虽然简单,但细节多、坑也不少
  • 尤其是对象、数组、多个 state 管理,值得认真掌握

掌握 useState ,是迈入 React 世界的第一步,也是之后理解 useEffectuseReducer 的基础。

希望这篇文章能帮你快速掌握 useState ,如果你觉得有帮助,别忘了点个赞👍或关注我后续的 重学 React 系列!

相关推荐
sunbyte4 分钟前
Three.js + React 实战系列 - 职业经历区实现解析 Experience 组件✨(互动动作 + 3D 角色 + 点击切换动画)
javascript·react.js·3d
2401_8319433212 分钟前
Element Plus对话框(ElDialog)全面指南:打造灵活弹窗交互
前端·vue.js·交互
strongwyy18 分钟前
DA14585墨水屏学习(2)
前端·javascript·学习
好青崧32 分钟前
冒泡排序的原理
前端
完美世界的一天40 分钟前
ES面试题系列「一」
大数据·elasticsearch·搜索引擎·面试·全文检索
椒盐螺丝钉41 分钟前
CSS 基础知识分享:从入门到注意事项
前端·css
冬阳春晖1 小时前
web animation API 锋利的css动画控制器 (更新中)
前端·javascript·css
Python私教2 小时前
使用FastAPI和React以及MongoDB构建全栈Web应用05 FastAPI快速入门
前端·react.js·fastapi
浪裡遊2 小时前
Typescript中的对象类型
开发语言·前端·javascript·vue.js·typescript·ecmascript
杨-羊羊羊2 小时前
什么是深拷贝什么是浅拷贝,两者区别
开发语言·前端·javascript