likes
comments
collection
share

useState你真的会用嘛

作者站长头像
站长
· 阅读数 58

在 Code Review 时,常常会看到一些 useState 使用过程中的错误。本文将记录这些常见错误并分享正确的使用方法,希望对大家有所帮助。

1.使用了历史状态进行计算

一个常见的错误是在更新状态时没有考虑到历史状态。来看下面这个计数器组件,每次点击按钮状态加 1:

import React, { useState } from 'react';

const CounterComponent = () => {
  const [counter, setCounter] = useState(0);

  const incrementCounter = () => {
    setCounter(counter + 1);
  };

  return (
    <div>
      <p>Counter: {counter}</p>
      <button onClick={incrementCounter}>Increment</button>
    </div>
  );
};

export default CounterComponent;

在上面的代码中,正常点击按钮看起来是没有问题的。但是在一些特殊情况下,例如连续快速点击按钮或者有其他状态更新时,React 可能会将多个 setCounter 合并处理,导致状态更新不准确。

正确的做法: setCounter 可以接收一个函数,该函数会传入上一次的状态值,我们应使用这个状态值进行计算。

import React, { useState } from 'react';

const CounterComponent = () => {
  const [counter, setCounter] = useState(0);

  const incrementCounter = () => {
    setCounter(prevCounter => prevCounter + 1);
  };

  return (
    <div>
      <p>Counter: {counter}</p>
      <button onClick={incrementCounter}>Increment</button>
    </div>
  );
};

export default CounterComponent;

改造后,我们可以看到 setCounter 中使用了一个函数来更新状态,这个函数接收当前最新的状态值,并返回更新后的值。这种方法更可靠,即使在连续点击或有其他状态更新时也不会漏加。

2.直接修改对象或者数组中的值页面没有刷新

直接修改对象或数组中的值不会触发 React 的重新渲染。来看一个示例:

import React, { useState } from 'react';

const ListComponent = () => {
  const [items, setItems] = useState([1, 2, 3]);

  const updateItem = () => {
    items[0] = 100; // 直接修改数组
    setItems(items); // 传递相同的数组引用
  };

  return (
    <div>
      <ul>
        {items.map(item => (
          <li key={item}>{item}</li>
        ))}
      </ul>
      <button onClick={updateItem}>Update Item</button>
    </div>
  );
};

export default ListComponent;

在这个例子中,尽管我们修改了数组中的值,但页面不会重新渲染,因为我们传递了相同的数组引用。

正确的做法: 使用扩展运算符创建一个新数组。

import React, { useState } from 'react';

const ListComponent = () => {
  const [items, setItems] = useState([1, 2, 3]);

  const updateItem = () => {
    const newItems = [...items];
    newItems[0] = 100; // 修改新数组
    setItems(newItems); // 传递新数组引用
  };

  return (
    <div>
      <ul>
        {items.map(item => (
          <li key={item}>{item}</li>
        ))}
      </ul>
      <button onClick={updateItem}>Update Item</button>
    </div>
  );
};

export default ListComponent;

改造后,每次状态更新都会创建一个新数组,从而触发组件重新渲染。

3.在一个方法中多次调用同一个setState 被合并处理

在一个方法中多次调用同一个 setState 时,React 可能会将这些调用合并处理。来看一个示例:

import React, { useState } from 'react';

const MultipleSetStateComponent = () => {
  const [count, setCount] = useState(0);

  const updateCount = () => {
    setCount(count + 1);
    setCount(count + 2);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={updateCount}>Update Count</button>
    </div>
  );
};

export default MultipleSetStateComponent;

在这个示例中,点击按钮后 count 的值并不会如预期的那样增加 3,因为 React 会将多次 setCount 调用合并。

正确的做法: 使用函数形式的 setState 来确保状态更新是基于最新的状态值。

import React, { useState } from 'react';

const MultipleSetStateComponent = () => {
  const [count, setCount] = useState(0);

  const updateCount = () => {
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 2);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={updateCount}>Update Count</button>
    </div>
  );
};

export default MultipleSetStateComponent;

4.多余的state

有时候我们会在组件中定义一些不必要的状态,这会增加代码的复杂性并降低性能。来看一个示例:

import React, { useState } from 'react';

const RedundantStateComponent = () => {
  const [inputValue, setInputValue] = useState('');
  const [isInputEmpty, setIsInputEmpty] = useState(true);

  const handleInputChange = (e) => {
    setInputValue(e.target.value);
    setIsInputEmpty(e.target.value === '');
  };

  return (
    <div>
      <input type="text" value={inputValue} onChange={handleInputChange} />
      {isInputEmpty && <p>The input is empty</p>}
    </div>
  );
};

export default RedundantStateComponent;

在这个示例中,isInputEmpty 状态是多余的,因为它完全可以通过 inputValue 计算出来。

正确的做法: 移除多余的状态,通过计算得出需要的值。

import React, { useState } from 'react';

const OptimizedStateComponent = () => {
  const [inputValue, setInputValue] = useState('');

  const handleInputChange = (e) => {
    setInputValue(e.target.value);
  };

  const isInputEmpty = inputValue === '';

  return (
    <div>
      <input type="text" value={inputValue} onChange={handleInputChange} />
      {isInputEmpty && <p>The input is empty</p>}
    </div>
  );
};

export default OptimizedStateComponent;

改造后,我们移除了多余的 isInputEmpty 状态,通过计算 inputValue 是否为空来直接决定是否显示提示信息,从而简化了代码。

总结

通过上述几个常见错误的示例和优化方法,我们可以看到,正确使用 useState 不仅能避免常见的坑,还能显著提升代码的稳定性和可维护性。以下是一些关键的总结和最佳实践:

1. 理解状态更新的异步性

useState 更新状态是异步的,这意味着状态更新后并不会立即生效。在处理依赖于前一个状态的更新时,直接使用当前状态值可能会导致问题。因此,使用函数形式的 setState 是一个更好的选择。这种形式接收前一个状态值作为参数,并返回新的状态值,确保状态更新的正确性和一致性。

2. 保持状态不变性

在 React 中,直接修改状态对象或数组中的值是一个常见错误。这种做法不会触发组件的重新渲染。为了保持状态的不可变性,我们应该总是创建状态的副本并进行修改,然后将副本设置为新的状态。使用扩展运算符(...)是一个常见且推荐的方式来实现这一点。

3. 避免同一个地方多次调用同一个 setState 导致的状态合并

在一个方法中多次调用同一个 setState 可能会导致状态合并,从而无法按预期更新状态。为了解决这个问题,我们可以使用函数形式的 setState 来确保每次状态更新都是基于最新的状态值。

4. 避免多余的状态

在开发过程中,定义过多的状态不仅会增加代码的复杂性,还会影响性能。如果一个状态完全可以通过现有的状态派生出来,那么这个状态就是多余的。我们应尽量避免这种情况,保持状态的简洁和必要。

5. 使用 useState 的惰性初始化优化性能

在某些情况下,初始化状态可能需要进行复杂的计算。为了避免每次渲染都执行这些计算,可以使用 useState 的惰性初始化特性。将初始化逻辑放在函数中,并将该函数传递给 useState,这样只有在初始渲染时才会调用该函数,从而优化性能。

6. 关注状态更新的频率和粒度

状态更新的频率和粒度直接影响组件的性能。尽量减少不必要的状态更新,确保每次更新都是必要且有意义的。可以使用 useMemo 和 useCallback 等 Hook 来优化性能,避免由于状态更新导致的过多重新渲染。

转载自:https://juejin.cn/post/7371370680515264550
评论
请登录