【React】use-immer vs 原生 Hook:谁更胜一筹?

1.概述

  1. use-immer 不属于官方 Hook,是社区维护的第三方库!
  2. use-immer 通过封装 Immer 的不可变更新机制,为 React 开发者提供了一种更直观、高效的状态管理方式。
  3. 它尤其适合处理复杂嵌套状态或需要频繁更新的场景,同时保持了与 React 原生 Hook 的兼容性。
  4. 对于追求代码简洁性和可维护性的项目,use-immer 是一个值得尝试的解决方案。

PS:

  1. 为了解决 useStateuseReducer, 操作对象及数据非常的麻烦的问题!有了 use-immer 之后就不在需要这么麻烦了!
  2. 因为它的底层是基于 immer.js 这个库去实现的!immer.js 库可以高效的去复制一个对象,并且在更改新的草稿对象的时候不会修改原始对象!
  3. 正好合 React 的理念相符合!所以说在工作中更多的应用中使用最多的就是这个库。

2.安装

pnpm 或者 npm 安装

bash 复制代码
// pnpm 安装
pnpm add use-immer

// npm 安装
npm install use-immer

3.使用指南

  • 以下通过多个实际场景展示 use-immer 的强大功能

  • 涵盖基础状态更新、复杂对象/数组操作、表单管理以及与 useImmerReducer 的结合使用。

3.1 基础计数器

直接修改数值

说明

  • 直接传递新值(如 count + 1)给 setCount,行为类似 useState,适合简单状态更新。
js 复制代码
import React from "react";
import { useImmer } from "use-immer";

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
    </div>
  );
}

3.2 复杂对象更新

深层嵌套修改简化

js 复制代码
import { useImmer } from 'use-immer';

interface User {
  name: string;
  address: {
    city: string;
    zip: string;
  };
}

function App() {
  const [user, updateUser] = useImmer<User>({
    name: "Alice",
    address: {
      city: "New York",
      zip: "10001",
    },
  });

  const updateCity = () => {
    updateUser(draft => {
      // 使用 Immer 的 draft 草稿对象直接修改嵌套的城市字段,无需手动复制其他字段
      draft.address.city = "Los Angeles";
    });
  };
  return (
    <div>
      <p>Name: {user.name}</p>
      <p>City: {user.address.city}</p>
      <button onClick={updateCity}>Change City</button>
    </div>
  );
}
export default App;

对比原生 useState

js 复制代码
// 原生方式需手动展开所有嵌套层级
const [user, setUser] = useState(initialUser);
setUser({
  ...user,
  address: {
    ...user.address,
    city: "Los Angeles",
  },
});

3.3 处理数组

数组操作变得异常简单,所有原生数组方法都可以直接使用

js 复制代码
import { useImmer } from 'use-immer';

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

function App() {
  // 修正初始值类型,应为 User[] 数组类型
  const [arr, updatArr] = useImmer<User[]>([
    {
      name: "Alice",
      age: 18,
    }
  ]);

  const updateCity = (i: User) => {
    updatArr(draft => {
      // 新增数据
      draft.push(i);
      // PS: 这里也可以直接修改值 draft[0].age = 60;
    });
  };
  return (
    <div>
      {
        Array.isArray(arr) && arr.map((item, index) => (
          <div key={index}>
            <p>Name: {item.name}</p>
            <p>Age: {item.age}</p>
          </div>
        ))
      }
      <button onClick={() => updateCity({ name: "Bob", age: 20 })}>Add User</button>
    </div>
  );
}
export default App;

3.4 表单管理

优势

  • 无需为每个字段单独维护 useState,代码更简洁。
  • 动态字段更新(如 draft[name] = value)避免冗余逻辑。
js 复制代码
function ContactForm() {
  const [form, updateForm] = useImmer({
    name: "",
    email: "",
    phone: "",
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    updateForm(draft => {
      draft[name] = value; // 动态更新任意字段
    });
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log("Form submitted:", form);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        name="name" 
        value={form.name} 
        onChange={handleChange} 
        placeholder="Name" 
      />
      <input 
        name="email" 
        value={form.email} 
        onChange={handleChange} 
        placeholder="Email" 
      />
      <input 
        name="phone" 
        value={form.phone} 
        onChange={handleChange} 
        placeholder="Phone" 
      />
      <button type="submit">Submit</button>
    </form>
  );
}

3.5 高级场景:useImmerReducer 管理复杂状态

为什么选择 useImmerReducer

  • 集中管理相关状态逻辑
  • 避免 useReducer 中手动展开状态的繁琐操作。
js 复制代码
import { useImmerReducer } from 'use-immer';

// 定义状态类型,增加点属性
interface CounterState {
  name: string;
  age: number;
}

// 定义 action 类型,增加与点属性相关的 action
type CounterAction = { type: 'increment' } | { type: 'decrement' } | { type: 'reset' };

// 定义 reducer 函数,增加点属性的处理逻辑
function counterReducer(draft: CounterState, action: CounterAction): CounterState { // 增加返回值类型声明
  switch (action.type) {
    case 'increment':
      draft.age++;
      break;
    case 'decrement':
      draft.age--;
      break;
    case 'reset':
      draft.age = 0;
      break;
  }
  return draft; // 明确返回 draft
}

function App() {
  // 使用 useImmerReducer 初始化状态,将初始化值改为对象形式,增加点属性的初始化
  const initialState: CounterState = { name: 'Jon', age: 18 }; // 增加类型注解
  const [state, dispatch] = useImmerReducer(counterReducer, initialState);
  const { name, age } = state;

  return (
    <div>
      <p>姓名: {name}</p>
      <p>年龄: {age ?? 0}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>增加</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>减少</button>
      <button onClick={() => dispatch({ type: 'reset' })}>重置</button>
    </div>
  );
}

export default App;

4.总结

use-immer 通过隐藏不可变更新的复杂性,让开发者能更专注于业务逻辑,尤其适合中大型 React 项目。

use-immer 与原生 useState/useReducer 对比?

特性 use-immer useState useReducer
状态更新方式 直接修改草稿(draft 返回新状态对象 返回新状态对象
深层嵌套处理 无需手动展开,直接修改 需手动展开或使用工具库(如 immer 需手动展开或使用工具库(如 immer
代码可读性 高(类似直接赋值) 低(需处理嵌套) 中(需编写 reducer 逻辑)
性能优化 自动生成不可变更新,减少重渲染 依赖开发者优化 依赖开发者

何时使用 use-immer

场景 推荐 Hook
简单状态(数值、字符串) useState
深层嵌套对象/数组更新 useImmer
表单或多字段同步管理 useImmer
复杂状态逻辑(如购物车) useImmerReducer