State

State (状态) 是组件内部的数据,可以随时间变化

什么是 State?

State 是组件的内存。它用于存储组件在渲染过程中需要记住的信息。 当 State 改变时,React 会重新渲染组件。

TypeScript
import { useState } from 'react';

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

  return (
    <button onClick={() => setCount(count + 1)}>
      点击了 {count} 次
    </button>
  );
}

State vs Props

Props 和 State 的主要区别:

  • Props:像函数的参数,由父组件传递,只读
  • State:像函数的局部变量,组件内部管理,可变
TypeScript
// Props - 外部传入
function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

// State - 内部管理
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
核心概念

State 是私有的封装的。 除非你显式地将它作为 Props 传递给子组件, 否则其他组件无法访问它。

使用 State

声明 State

使用 useState Hook 声明 State:

TypeScript
import { useState } from 'react';

function Example() {
  // 语法:const [state, setState] = useState(initialValue)
  const [count, setCount] = useState(0);
  const [name, setName] = useState('Taylor');
  const [isActive, setIsActive] = useState(false);
  const [user, setUser] = useState({ name: 'Taylor', age: 25 });

  // ...
}

TypeScript 类型注解

使用 TypeScript 时,你可以为 State 添加类型注解以获得更好的类型安全:

TypeScript
import { useState } from 'react';

// 基本类型
const [count, setCount] = useState<number>(0);
const [name, setName] = useState<string>('');
const [isActive, setIsActive] = useState<boolean>(false);

// 对象类型 - 使用 interface
interface User {
  name: string;
  age: number;
  email?: string;
}

const [user, setUser] = useState<User>({
  name: 'Taylor',
  age: 25
});

// 联合类型
type Status = 'idle' | 'loading' | 'success' | 'error';
const [status, setStatus] = useState<Status>('idle');

// 数组类型
const [items, setItems] = useState<string[]>([]);
const [users, setUsers] = useState<User[]>([]);

// 使用类型推断(推荐)
// TypeScript 会自动从初始值推断类型
const [count, setCount] = useState(0); // 推断为 number

读取 State

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

  return (
    <div>
      <p>当前计数: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        增加
      </button>
    </div>
  );
}

更新 State

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

  const increment = () => {
    // 直接更新
    setCount(count + 1);

    // 或者使用函数更新
    setCount(prev => prev + 1);
  };

  return (
    <button onClick={increment}>
      计数:{count}
    </button>
  );
}
重要

永远不要直接修改 State。React 无法检测到直接修改, 必须使用设置函数来更新 State。

更新对象

当 State 是对象时,你需要创建一个新对象而不是修改现有对象:

TypeScript
const [user, setUser] = useState({
  name: 'Taylor',
  age: 25,
  email: 'taylor@example.com'
});

// ❌ 错误:直接修改
user.age = 26;

// ✅ 正确:创建新对象
setUser({
  ...user,
  age: 26
});

// ✅ 正确:使用函数更新
setUser(prevUser => ({
  ...prevUser,
  age: 26
}));

更新嵌套对象

TypeScript
const [person, setPerson] = useState({
  name: 'Taylor',
  address: {
    city: 'New York',
    country: 'USA'
  }
});

// 更新嵌套属性
setPerson({
  ...person,
  address: {
    ...person.address,
    city: 'Boston'
  }
});
简化嵌套更新

对于复杂的嵌套对象,可以使用 Immer 库来简化更新逻辑。 Immer 允许你编写看似直接修改的代码,但实际上会创建不可变更新。

更新数组

更新数组时,你需要创建一个新数组而不是修改现有数组:

添加元素

TypeScript
const [items, setItems] = useState([1, 2, 3]);

// 添加到末尾
setItems([...items, 4]);

// 添加到开头
setItems([0, ...items]);

删除元素

TypeScript
// 使用 filter 删除
setItems(items.filter(item => item !== 2));

// 使用 slice 删除
setItems([
  ...items.slice(0, index),
  ...items.slice(index + 1)
]);

更新元素

TypeScript
// 使用 map 更新
setItems(items.map((item, i) =>
  i === index ? newValue : item
));

插入元素

TypeScript
// 在指定位置插入
setItems([
  ...items.slice(0, index),
  newItem,
  ...items.slice(index)
]);
数组方法

以下数组方法会修改原数组,不要使用:

  • push()pop()
  • splice()
  • sort()reverse()

以下数组方法会返回新数组,可以使用:

  • map()filter()
  • slice()concat()
  • spread ([...arr])

函数式更新

当新 State 依赖于旧 State 时,使用函数式更新:

TypeScript
const [count, setCount] = useState(0);

// ❌ 可能有问题:基于旧 State 更新
const increment = () => {
  setCount(count + 1);
  setCount(count + 1);
  // 两次都基于同一个 count 值,结果只增加 1
};

// ✅ 正确:使用函数更新
const increment = () => {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
  // 每次都基于最新的值,结果增加 2
};
最佳实践

当新 State 依赖于旧 State 时,总是使用函数式更新。 这可以避免由于 State 闭包导致的问题。

多个 State 变量

你可以使用多个 State 变量,或者将相关数据组合成一个对象:

多个独立变量

TypeScript
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [email, setEmail] = useState('');

// 更新时互不影响
setName('Taylor');
setAge(25);

单个对象

TypeScript
const [user, setUser] = useState({
  name: '',
  age: 0,
  email: ''
});

// 更新时需要合并其他属性
setUser({ ...user, name: 'Taylor' });

如何选择?

使用多个 State 变量,当:

  • 数据是相互独立的
  • 数据不会一起更新
  • 数据是不同的类型

使用单个对象,当:

  • 数据是相关的
  • 数据经常一起更新
  • 数据是相同类型

初始 State

直接初始化

TypeScript
function Counter() {
  const [count, setCount] = useState(0);
  // ...
}

惰性初始化

如果初始状态需要通过复杂计算得出,使用函数可以避免每次渲染时重新计算:

TypeScript
function createInitialCount() {
  console.log('这只运行一次');
  return computeExpensiveValue();
}

function Counter() {
  const [count, setCount] = useState(createInitialCount);
  // ...
}

或者使用内联函数:

TypeScript
const [count, setCount] = useState(() => {
  console.log('这只运行一次');
  return computeExpensiveValue();
});
性能优化

传递给 useState 的函数只在初始渲染时执行一次, 后续渲染会被跳过。这比直接在组件内部计算更高效。

State 更新批处理

React 会将多个 State 更新批处理为一次重新渲染,以提高性能:

TypeScript
function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');

  const handleClick = () => {
    setCount(count + 1);
    setName('Taylor');
    // 这两个更新会被批处理为一次渲染
  };

  // ...
}

React 18 之前 vs 之后

在 React 18 中,自动批处理的范围更广,包括事件处理器、Promise、setTimeout 等:

TypeScript
// React 18 - 所有这些更新都会被批处理
function handleClick() {
  setCount(c => c + 1);
  setName('Taylor');

  fetch('/api').then(() => {
    setFlag(true);
    setCount(c => c + 1);
    // 这里也会被批处理
  });
}
强制刷新

如果你需要在 State 更新后立即读取新值,可以使用 flushSync

TypeScript
import { flushSync } from 'react-dom';

flushSync(() => {
  setCount(count + 1);
});
// 现在可以立即读取新的 count 值

常见错误

直接修改 State

TypeScript
// ❌ 错误
items.push(newItem);
setItems(items);

// ✅ 正确
setItems([...items, newItem]);

在渲染中更新 State

TypeScript
// ❌ 错误:无限循环
function Counter() {
  const [count, setCount] = useState(0);

  setCount(count + 1); // 不要在渲染中更新

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

// ✅ 正确:在事件处理器中更新
function Counter() {
  const [count, setCount] = useState(0);

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

使用旧 State

TypeScript
// ❌ 可能有问题
const increment = () => {
  setCount(count + 1);
  setCount(count + 1);
};

// ✅ 正确:使用函数更新
const increment = () => {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
};

State 未初始化

TypeScript
// ❌ 错误:State 可能是 undefined
const [user, setUser] = useState();
return <div>{user.name}</div>;

// ✅ 正确:提供初始值
const [user, setUser] = useState({ name: '', age: 0 });
return <div>{user.name}</div>;

最佳实践

1. 保持 State 简单

只将渲染需要的数据放入 State。其他数据可以从 State 计算得出:

TypeScript
// ❌ 冗余的 State
const [items, setItems] = useState([1, 2, 3]);
const [totalCount, setTotalCount] = useState(items.length);

// ✅ 计算得出的值
const [items, setItems] = useState([1, 2, 3]);
const totalCount = items.length;

2. 避免重复的 State

TypeScript
// ❌ 重复的 State
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');

// ✅ 计算得出
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = `${firstName} ${lastName}`;

3. 避免深层嵌套的 State

如果 State 过于复杂,考虑使用 useReducer:

TypeScript
// 使用 useReducer 管理复杂状态
const [state, dispatch] = useReducer(reducer, {
  user: { name: '', age: 0 },
  posts: [],
  comments: []
});

4. 使用 TypeScript 类型

TypeScript
interface User {
  name: string;
  age: number;
  email: string;
}

const [user, setUser] = useState<User>({
  name: '',
  age: 0,
  email: ''
});

何时使用 State?

使用 State 当:

  • 数据随时间变化
  • 数据不能从 Props 计算得出
  • 数据是组件私有的
  • 数据会影响渲染输出

不使用 State 当:

  • 数据可以从 Props 计算得出
  • 数据可以从其他 State 计算得出
  • 数据不会改变
  • 数据不影响渲染
判断标准

问自己:“这个数据改变时,组件需要重新渲染吗?” 如果答案是“是”,使用 State。如果答案是“否”,使用普通变量或 ref。

React 19 新增:表单状态管理

React 19 引入了专门用于表单状态管理的新 Hooks,可以简化表单处理:

useActionState

useActionState 用于管理表单提交的状态(pending、error、success):

TypeScript
import { useActionState } from 'react';

function Form() {
  const [state, formAction, isPending] = useActionState(
    async (prevState, formData) => {
      // 提交表单
      const result = await submitForm(formData);
      return result;
    },
    null // 初始状态
  );

  return (
    <form action={formAction}>
      <input name="email" type="email" />
      <button type="submit" disabled={isPending}>
        {isPending ? '提交中...' : '提交'}
      </button>
      {state?.error && <div className="error">{state.error}</div>}
    </form>
  );
}

useOptimistic

useOptimistic 用于实现乐观更新(在服务器响应前先显示更新后的 UI):

TypeScript
import { useOptimistic } from 'react';

function LikeButton({ likes, onLike }) {
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    likes,
    (state, newLike) => state + 1
  );

  return (
    <button
      onClick={() => {
        addOptimisticLike({ type: 'like' });
        onLike();
      }}
    >
      ❤️ {optimisticLikes}
    </button>
  );
}
React 19 新特性

这些新 Hooks 专门用于表单处理乐观更新场景。 对于普通的状态管理,useState 仍然是首选。

相关 Hooks

这篇文章有帮助吗?