react中hooks-你真的会用useState吗?

72 阅读3分钟

useState参数的类型可以是基本数据类型,还可以是复杂类型,复杂类型中我们介绍一下数组, 在 React 中使用 useState 管理数组时,直接调用数组方法修改原数组会导致视图无法更新,这是由 React 的状态更新机制和不可变性原则决定的。以下是具体原因及解释:


一、React 的浅比较机制

React 在状态更新时通过 ​浅比较(Shallow Comparison)​​ 判断状态是否变化。如果新旧状态的引用地址相同,React 会认为状态未改变,从而跳过重新渲染。

例如:

const [list, setList] = useState([1, 2, 3]);
list.push(4); // 直接修改原数组
setList(list); // 引用未变,视图不更新

此时 list 的引用地址与原数组相同,React 检测不到变化,导致视图未更新。


二、数组方法的副作用

许多 JavaScript 数组方法(如 pushpopreversesort)​直接修改原数组,而非返回新数组。这违反了 React 的 ​不可变性原则,即状态更新应通过创建新数据实现,而非直接修改原数据。

例如:

// 错误:直接修改原数组
list.reverse(); // 原数组被修改,但引用地址不变
setList(list); // 视图不更新

// 正确:创建新数组
const newList = [...list].reverse();
setList(newList); // 引用地址改变,触发渲染

三、不可变性的优势

  1. 可预测性
    通过创建新数组,可以明确追踪状态变化路径,避免因直接修改原数据导致的意外副作用。
  2. 性能优化
    React 依赖浅比较快速判断是否需要重新渲染。若直接修改原数组,即使数据内容变化,引用地址未变会导致 React 无法触发更新,或依赖深比较带来性能损耗。
  3. 兼容 React 特性
    不可变性是 React 高阶功能(如 useMemouseCallback)和性能优化组件(如 React.memoPureComponent)的基础。

四、如何正确更新数组状态?

  1. 使用返回新数组的方法

    • 扩展运算符:const newList = [...list, newItem];
    • map/filter/sliceconst filtered = list.filter(item => item > 2);
    • 拷贝后修改:const reversed = [...list].reverse();
  2. 避免直接修改嵌套数组
    若数组元素是对象或其他引用类型,需同时保证嵌套数据的不可变性:

    // 错误:直接修改嵌套对象
    const newList = [...list];
    newList[0].name = "Bob"; // 修改了原对象的引用
    setList(newList);
    
    // 正确:创建新对象
    const newList = list.map((item, index) => 
      index === 0 ? { ...item, name: "Bob" } : item
    );
    setList(newList);
    
  3. 使用不可变库(如 Immer)​
    Immer 可通过“草稿”模式简化不可变操作:

    import produce from "immer";
    const newList = produce(list, draft => {
      draft.push(4); // 直接操作草稿,自动生成新数组
    });
    setList(newList);
    

五、常见错误场景

  1. 异步更新问题
    在异步操作(如事件回调、定时器)中直接修改原数组,可能导致状态更新不同步:

    
    // 错误:异步中直接修改原数组
    setTimeout(() => {
      list.push(4);
      setList(list); // 可能不触发更新
    }, 1000);
    
    // 正确:使用函数式更新
    setTimeout(() => {
      setList(prev => [...prev, 4]); // 基于最新状态创建新数组
    }, 1000);
    
  2. 批量更新失效
    React 可能合并多个 setState 调用,若直接修改原数组,可能导致更新丢失:

    
    // 错误:三次修改可能被合并
    list.push(1); setList(list);
    list.push(2); setList(list);
    list.push(3); setList(list);
    
    // 正确:函数式更新保证独立性
    setList(prev => [...prev, 1]);
    setList(prev => [...prev, 2]);
    setList(prev => [...prev, 3]);
    

总结

在 React 中直接修改 useState 数组会导致视图不更新,因为:

  1. 浅比较机制依赖引用地址
  2. 不可变性原则要求创建新数据
  3. 直接修改会破坏 React 的性能优化逻辑

通过使用扩展运算符、map/filter 等方法创建新数组,或借助 Immer 等工具,可以既保证代码的可维护性,又符合 React 的设计哲学。