《Go 后端开发者学 React:useState》

React useState 完全指南:从基础用法到实际开发

React + TypeScript 学习笔记 · Hooks · 状态管理

一、useState 是什么?

useState 是 React 提供的一个 Hook,用于在函数组件中保存状态,并在状态更新后触发页面重新渲染。

简单理解:

  • 普通变量:数据变化,不会主动通知 React 更新页面。
  • useState:数据变化后,React 会根据新状态更新页面。

1.1 最简单的例子

javascript 复制代码
import { useState } from "react";

function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>{count}</h1>

      <button onClick={() => setCount(count + 1)}>
        +1
      </button>
    </div>
  );
}

核心语法:

scss 复制代码
const [count, setCount] = useState(0);
部分 含义
useState(0) 创建状态,初始值为 0
count 当前状态值
setCount 更新状态的函数

执行过程:

scss 复制代码
点击按钮
   ↓
调用 setCount(count + 1)
   ↓
React 保存新状态
   ↓
重新渲染组件
   ↓
页面显示新的 count

1.2 为什么使用 const?

你可能会疑惑:

scss 复制代码
const [count, setCount] = useState(0);

既然 count 是 const,为什么值还能改变?

因为 React 并不是修改原来的 count,而是在每次重新渲染时,重新执行组件函数并获得新的状态值。

ini 复制代码
// 第一次渲染
const count = 0;

// 调用 setCount(1) 后

// 第二次渲染
const count = 1;

这只是原理示意,并不是真正执行的代码。

二、useState 的 6 种常见用法

2.1 数字状态:计数器

这是最基础的用法。

javascript 复制代码
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState<number>(0);

  return (
    <div>
      <h2>当前计数:{count}</h2>

      <button onClick={() => setCount(count + 1)}>
        +1
      </button>

      <button onClick={() => setCount(count - 1)}>
        -1
      </button>

      <button onClick={() => setCount(0)}>
        重置
      </button>
    </div>
  );
}

实际效果

当前计数

0

减 1 加 1重置

这里的 <number> 是 TypeScript 泛型,表示状态只能是 number 类型。

scss 复制代码
const [count, setCount] = useState<number>(0);

setCount(10);      // ✅
setCount("hello"); // ❌ 类型错误

其实这里也可以省略 <number>,TypeScript 会根据初始值 0 自动推断。

2.2 字符串状态:输入框

常用于表单、搜索框、用户名输入等。

javascript 复制代码
import { useState } from "react";

function UserInput() {
  const [name, setName] = useState("");

  return (
    <div>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
      />

      <p>你好,{name}</p>
    </div>
  );
}

实际效果

请输入姓名

你好,请输入姓名

重点理解:

scss 复制代码
onChange={(e) => setName(e.target.value)}

执行逻辑:

  1. 用户在输入框输入内容。
  2. 触发 onChange 事件。
  3. 通过 e.target.value 获取输入内容。
  4. 调用 setName() 更新状态。
  5. React 重新渲染,页面展示新内容。

这里的输入框属于 React 的受控组件(Controlled Component),因为输入值由 React 状态控制。

2.3 布尔状态:显示与隐藏

常用于弹窗、加载状态、开关等。

javascript 复制代码
function Toggle() {
  const [visible, setVisible] = useState(false);

  return (
    <div>
      <button onClick={() => setVisible(!visible)}>
        切换显示
      </button>

      {visible && <p>这是一段隐藏内容</p>}
    </div>
  );
}

实际效果

显示详情

显示

核心代码:

scss 复制代码
setVisible(!visible);

表示把状态取反:

  • false → true
  • true → false

2.4 对象状态:修改用户信息

实际业务中,很多状态都是对象。

例如:

typescript 复制代码
interface User {
  id: number;
  name: string;
  age: number;
}

function UserProfile() {
  const [user, setUser] = useState<User>({
    id: 1001,
    name: "张三",
    age: 25
  });

  const changeName = () => {
    setUser({
      ...user,
      name: "李四"
    });
  };

  return (
    <div>
      <p>姓名:{user.name}</p>
      <p>年龄:{user.age}</p>

      <button onClick={changeName}>
        修改姓名
      </button>
    </div>
  );
}

这里最关键的是:

css 复制代码
setUser({
  ...user,
  name: "李四"
});

...user 是 JavaScript 的展开语法,用于复制对象中的属性。

相当于:

php 复制代码
setUser({
  id: user.id,
  name: "李四",
  age: user.age
});

为什么不能直接修改?

错误写法:

ini 复制代码
user.name = "李四"; // ❌ 直接修改状态对象

React 状态应该按不可变数据来处理。直接修改原对象不会主动触发重新渲染,还可能导致状态不一致。

正确写法是创建新对象:

css 复制代码
setUser({
  ...user,
  name: "李四"
});

如果新值依赖旧值,也可以这样写:

ini 复制代码
setUser(prev => ({
  ...prev,
  age: prev.age + 1
}));

2.5 数组状态:任务列表(重点)

实际开发里,经常需要保存一个列表。

例如任务管理系统:

typescript 复制代码
interface Task {
  id: number;
  name: string;
  status: string;
}

function TaskList() {
  const [tasks, setTasks] = useState<Task[]>([
    { id: 1, name: "视频回查", status: "running" },
    { id: 2, name: "文章回扫", status: "pending" }
  ]);

  const addTask = () => {
    setTasks(prev => [
      ...prev,
      {
        id: 3,
        name: "评论治理",
        status: "pending"
      }
    ]);
  };

  return (
    <div>
      {tasks.map(task => (
        <p key={task.id}>
          {task.name} - {task.status}
        </p>
      ))}

      <button onClick={addTask}>
        添加任务
      </button>
    </div>
  );
}

核心:

ini 复制代码
setTasks(prev => [...prev, newTask]);

表示:

  1. 获取旧数组 prev。
  2. 使用 ...prev 复制旧数组内容。
  3. 添加 newTask。
  4. 生成一个新数组。
  5. 触发 React 更新。

实际效果

任务列表

2 条任务

视频回查

running

文章回扫

pending

添加任务重置列表

数组状态常见的几种操作:

操作 推荐写法
添加 setList(p => [...p, item])
删除 setList(p => p.filter(x => x.id !== id))
修改 setList(p => p.map(x => ...))
清空 setList([])

例如修改任务状态:

ini 复制代码
setTasks(prev =>
  prev.map(task =>
    task.id === 1
      ? { ...task, status: "success" }
      : task
  )
);

不推荐:

scss 复制代码
tasks.push(newTask); // ❌ 直接修改原数组
setTasks(tasks);     // ❌ 仍然是原来的数组引用

因为 React 可能认为状态没有变化,跳过更新。

2.6 联合类型状态:加载状态

这个场景在 TypeScript 中很常见。

javascript 复制代码
type LoadingStatus =
  | "idle"
  | "loading"
  | "success"
  | "error";

function DataLoader() {
  const [status, setStatus] =
    useState<LoadingStatus>("idle");

  return (
    <div>
      <p>当前状态:{status}</p>

      <button onClick={() => setStatus("loading")}>
        开始加载
      </button>
    </div>
  );
}

这样可以限制状态值:

scss 复制代码
setStatus("success"); // ✅
setStatus("loading"); // ✅
setStatus("unknown"); // ❌

比直接使用 string 更安全,特别适合接口请求状态、任务执行状态。

三、useState 的两种更新方式

你可能注意到,前面的示例中出现了两种写法:

scss 复制代码
setCount(count + 1);

以及:

ini 复制代码
setCount(prev => prev + 1);

这两种有什么区别?

3.1 直接传入新值

scss 复制代码
setCount(10);

表示直接把状态更新为 10。

适合明确知道新值的场景。

3.2 传入更新函数(推荐用于依赖旧状态的情况)

ini 复制代码
setCount(prev => prev + 1);

其中 prev 表示 React 传入的前一个待更新状态。

例如:

scss 复制代码
function handleClick() {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
}

点击一次,通常只增加 1。

因为这三个 count 都是当前这次渲染中的同一个值。

改成:

ini 复制代码
function handleClick() {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
}

点击一次,就增加 3。

原因是 React 会依次处理这些状态更新函数。

记住:如果新状态依赖旧状态,优先使用 prev => ...。

四、useState 的初始化方式

4.1 直接初始化

scss 复制代码
const [count, setCount] = useState(0);

const [name, setName] = useState("张三");

这是最常用的方式。

4.2 惰性初始化

如果初始值需要复杂计算,可以传入一个函数:

scss 复制代码
const [data, setData] = useState(() => {
  return generateLargeData();
});

React 只在初始化状态时调用这个初始化函数,而不是每次渲染都调用。

注意,在开发模式 Strict Mode 下,React 可能会额外调用初始化函数,以检查是否存在不纯的逻辑。

五、useState 和 useEffect:如何获取后端数据?

这是业务开发中最典型的场景。

假设后端提供接口:

bash 复制代码
GET /api/tasks

返回:

css 复制代码
[  {    "id": 1,    "name": "视频回查",    "status": "running"  },  {    "id": 2,    "name": "文章回扫",    "status": "pending"  }]

React 示例:

typescript 复制代码
import { useEffect, useState } from "react";

interface Task {
  id: number;
  name: string;
  status: string;
}

function TaskList() {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchTasks() {
      try {
        const response = await fetch("/api/tasks");

        if (!response.ok) {
          throw new Error("请求失败");
        }

        const data: Task[] = await response.json();

        setTasks(data);
      } catch (error) {
        console.error(error);
      } finally {
        setLoading(false);
      }
    }

    fetchTasks();
  }, []);

  if (loading) {
    return <div>加载中...</div>;
  }

  return (
    <div>
      {tasks.map(task => (
        <div key={task.id}>
          {task.name} - {task.status}
        </div>
      ))}
    </div>
  );
}

这里的 Task[] 是对接口数据的类型标注,并不代表已经完成运行时数据校验。

重点理解三个概念:

  • useState:保存后端返回的数据。
  • useEffect:组件挂载后执行请求等副作用。
  • setTasks:数据返回后更新状态,触发页面重新渲染。

在真实项目中还需要考虑请求取消、错误展示和重复请求等问题。

六、useState 常见错误

错误一:直接修改状态

arduino 复制代码
count++; // ❌

正确:

ini 复制代码
setCount(prev => prev + 1);

错误二:直接修改对象或数组

ini 复制代码
user.name = "李四"; // ❌
tasks.push(newTask); // ❌

正确:

ini 复制代码
setUser(prev => ({
  ...prev,
  name: "李四"
}));

setTasks(prev => [...prev, newTask]);

错误三:在 if 中调用 useState

javascript 复制代码
function App({ visible }: { visible: boolean }) {
  if (visible) {
    const [count, setCount] = useState(0); // ❌
  }

  return <div>App</div>;
}

React Hook 必须在组件顶层调用,不能放在条件、循环或普通嵌套函数中。

正确:

javascript 复制代码
function App({ visible }: { visible: boolean }) {
  const [count, setCount] = useState(0);

  if (!visible) {
    return null;
  }

  return <div>{count}</div>;
}

错误四:以为 setState 后变量会立即变化

scss 复制代码
function handleClick() {
  setCount(count + 1);

  console.log(count);
}

这里输出的仍是当前渲染对应的旧 count。

这是因为每次渲染中的状态值可以看作一份快照。

七、useState 常见用法总结

状态类型 初始化 更新
数字 useState(0) setCount(c => c + 1)
字符串 useState("") setName("张三")
布尔值 useState(false) setOpen(v => !v)
对象 useState<User>(user) setUser(p => ({...p, name: "李四"}))
数组 useState<Task[]>([]) setTasks(p => [...p, task])
联合类型 useState<Status>("idle") setStatus("loading")
空值 `useState<User null>(null)`

八、最后总结

学习 React 的 useState,最重要的是理解以下四句话:

  1. useState 用来保存组件状态。
  2. 调用 setState 更新状态,会让 React 安排重新渲染。
  3. 数组和对象状态不要直接修改,应创建新数组或新对象。
  4. 新状态依赖旧状态时,优先使用函数式更新 prev => ...。

你可以把 React 的数据渲染理解成下面这个公式:

\\text{UI} = f(\\text{State})

也就是:页面 UI 是状态 State 的映射结果。

状态变化,React 重新计算组件应该显示什么,再把需要变化的部分更新到页面。

这正是 React 声明式编程的核心思想。

参考:React 官方文档 --- useState

相关推荐
Penge66622 分钟前
《Go 后端开发者学 React:数据渲染》
javascript
ikoala3 小时前
AI 写页面像说明书,修 Bug 像贴膏药?把这两句话写进 AGENTS.md
前端·javascript·ai编程
Penge6664 小时前
《Go 后端开发者学 TypeScript:ES Module了解》
javascript
Penge6664 小时前
《Go 后端开发者学 TypeScript:从零搭建与运行》
javascript
Penge6665 小时前
《Go 后端开发者学 TypeScript:keyof关键字》
javascript
Penge6665 小时前
《Go 后端开发者学 TypeScript:extends关键字》
javascript
张张123y5 小时前
DSH 插件调试避坑:client.js 设置面板、热重载与高频报错排查表
开发语言·javascript·人工智能·开源·ecmascript·后端开发·ai agent
Penge66610 小时前
《Go 后端开发者学 TypeScript:typescript 类型断言》
javascript
Penge66610 小时前
《Go 后端开发者学 TypeScript:Symbol》
javascript