React-状态管理进阶:useImmer进阶指南

56 阅读2分钟

前言

在 React 中,状态是不可变的(Immutable)。当我们使用 useState 处理深层嵌套的对象或大型数组时,频繁的结构解构会让代码变得支离破碎。useImmer 的出现,让我们能够以“声明式修改”的方式,享受“不可变数据”带来的性能红利。

一、 为什么需要 useImmer?(产生背景)

在原生 useState 中,更新一个深层嵌套的对象通常是痛苦的:

// useState 的痛苦写法
setUser(prev => ({
  ...prev,
  address: {
    ...prev.address,
    city: 'Beijing'
  }
}));

useImmer 的核心优势:

  1. 告别展开运算符:不再需要层层 {...state}
  2. 原生语法支持:可以直接使用 pushpopsplice 等会改变原数组的方法,而无需担心破坏不可变性。
  3. 代码高可读性:逻辑更接近于直接修改对象,代码简洁直观。

二、 基础语法与 TSX 实战

在使用前,请确保已安装:npm install immer use-immer

1. 处理复杂对象

在 TSX 中,建议为状态定义明确的 interface

import React from "react";
import { useImmer } from "use-immer";

interface UserProfile {
  id: number;
  info: {
    name: string;
    age: number;
  };
}

const UserSettings: React.FC = () => {
  const [user, setUser] = useImmer<UserProfile>({
    id: 1,
    info: { name: "Gemini", age: 20 }
  });

  const updateName = () => {
    // 这里的 draft 是一个 Proxy 代理对象
    setUser((draft) => {
      draft.info.name = "New Name"; // 直接修改属性,极其丝滑
    });
  };

  return (
    <div>
      <p>姓名: {user.info.name}</p>
      <button onClick={updateName}>修改姓名</button>
    </div>
  );
};

export default UserSettings;

2. 处理数组

无需再使用 [...list, newItem]

const ListDemo: React.FC = () => {
  const [list, setList] = useImmer<string[]>(["React", "Vue"]);

  const addItem = () => {
    setList((draft) => {
      draft.push("Angular"); // 直接 push,Immer 会自动生成新数组
    });
  };

  return <button onClick={addItem}>{list.join(", ")}</button>;
};

三、 核心避坑指南(注意事项)

1. 性能边界:何时不该用?

如果状态只是 数字、字符串、布尔值 等基本类型,请坚持使用 useState

  • 原因useImmer 内部基于 Proxy 实现。为简单类型创建代理对象会带来额外的内存开销和计算耗时,得不偿失。

2. Draft 的生命周期禁区

切记:不能将 draft 整体赋值给外部变量,或在异步闭包中使用。

  • 原理draft 是一个基于 Proxy 的临时代理。它的使命在 setState 的回调函数执行完毕后就结束了。
  • 后果:如果你尝试在修改函数之外持有 draft 的引用,会导致状态更新逻辑错乱,或者触发“非法访问代理”的报错。

四、 总结:useImmer 的心智模型

你可以把 useImmer 想象成在草稿纸Draft上改写数据。你随便涂抹、修改、删除,当你提交(回调结束)时,Immer 会根据你在草稿纸上的改动,为你生成一份全新的、不可变的正式文档。