放弃 useState 和 useReducer:为什么 useImmer 是更好的选择

2025-06-10

放弃 useState 和 useReducer:为什么 useImmer 是更好的选择

在 React 中,useStateuseReducer通常用于管理状态。虽然useImmer是 的流行替代方案useState,但在本文中,我们将探讨如何将其作为 的更简单、更有效的替代方案useReducer

 

目录

 

如果你不熟悉 useImmer

 

useImmer是一个自定义的 React Hook。它与 类似,useState但具有一些独特的优势,尤其是在管理复杂状态时。使用useImmer,您可以像常规 JavaScript 一样直接更新状态,就像它是直接可变的一样。这是可能的,因为在后台useImmer使用该Immer库创建了状态的新的不可变副本。

 

要使用useImmer,首先,您需要通过在终端中运行以下命令来安装它:

npm install immer use-immer
Enter fullscreen mode Exit fullscreen mode

 

一个简单的例子useImmer

/* importing useImmer */
import { useImmer } from 'use-immer';

/* functional component */
function Count() {

  /* Declaring a new state variable using the useImmer hook.
    Here, we are initializing our state with a single
    property called "count" with an initial value of 0 */

  const [state, updateState] = useImmer({ count: 0 });


  /* Defining a function that will modify our state */
  const increment = () => {
    /* calling the updateState function and passing as
       it a draft */
    updateState(draft => {
      /* Here we are directly modifying the draft as 
        if it were mutable */
      draft.count += 1;
    })
  }


  /* Defining another function that will modify our state */
  const decrement = () => {
    /* Calling the updateState function and pass it a draft */
    updateState(draft => {
      /*Here we are directly modifying the draft as 
        if it were mutable */
      draft.count -= 1;
    })
  }


  /* Rendering the UI using the state */
  return (
    <div>
      <h3>Count: {state.count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

 

⬆️ 返回目录

 

为什么我们应该使用 useImmer 而不是 useReducer

 

我们将探索一个useImmer遵循该useReducer模式的 示例。我们将创建状态和动作,并以与 类似的方式使用它们useReducer,但 useImmer 具有更高的可读性、灵活性和可变性。通过这个例子,我们将了解为什么useImmer应该比 更受重视useReducer

 

导入 useImmer 并创建函数组件

 

// import useImmer
import { useImmer } from 'use-immer';

// functional component
function Cart() {

  // ... 

}
Enter fullscreen mode Exit fullscreen mode

 

定义初始状态

 

使用 时useReducer,通常的做法是在继续操作之前定义初始状态。同样,我们将在useImmerhook 中使用这种方法。为此,我们将创建一个稍微复杂一些的状态,类似于您通常使用 定义的状态useReducer

// initial state
const initialState = {
    items: [],
    shippingAddress: {
        street: '',
        city: ''
    }
}  
Enter fullscreen mode Exit fullscreen mode

 

创建国家

 

我们已经定义了初始状态。现在,我们可以使用钩子来创建状态useImmer

// Creating the state
const [cart, updateCart] = useImmer(initialState)
Enter fullscreen mode Exit fullscreen mode

useImmer钩子返回一个包含两个值的数组:当前状态(cart在此示例中)和我们可以用来更新状态的函数(updateCart在此示例中)。

 

创建动作

 

使用useReducer钩子时,必须定义操作才能更新状态。同样,我们将创建操作来useImmer模拟相同的模式,并实现可预测的状态更新。

const actions = {

    // Add an item to the cart
    addItemToCart: (payload) => {

        const { item } = payload

        updateCart(draft => {
            draft.items.push(item)
        });
    },

    // Remove an item from the cart
    removeItemFromCart: (payload) => {

        const { itemIndex } = payload

        updateCart(draft => {
            draft.items.splice(itemIndex, 1)
        })
    },

    // Update the shipping address of the cart
    updateShippingAddress: (payload) => {

        const { shippingAddress } = payload

        updateCart(draft => {
            draft.shippingAddress = shippingAddress
        })
    }
}

Enter fullscreen mode Exit fullscreen mode

 

使用上述方法定义动作有两个优点useReducer

⚡️ 可变性:有了useImmer,我们拥有了可变更新的强大功能,可以减少代码量,并使其更像 JavaScript。这与useReducer需要更函数式编程方法和不可变更新的 形成了对比。通过在 中使用可变更新useImmer,您可以用更少的代码行实现相同的结果,从而更易于编写和维护。

⚡️ 可读性:与 相比useReducer,其中动作通常定义为switchreducer 函数中的案例,而该useImmer方法更具可读性,因为每个动作都是一个具有清晰简洁名称的单独函数。

 

使用 JSX 渲染 UI

 

最后,我们可以使用JSX 中的cart状态和actions对象来渲染 UI。

<div>

    {/* Displaying the changes of the 'cart' state */}
    <p>Cart State: {JSON.stringify(cart)}</p>

    {/* Display a list of items in the cart */}
    <ul>
        {cart.items.map((item, index) => (
            <li key={index}>
                {item.name} - ${item.price}

                {/* Call the removeItemFromCart action when the remove button is clicked */}
                <button onClick={() => actions.removeItemFromCart({ itemIndex: index })}>Remove</button>
            </li>
        ))}
    </ul>

    {/* Call the addItemToCart action when the add item button is clicked */}
    <button onClick={() => actions.addItemToCart({ item: { name: 'Product', price: 9.99 } })}>
        Add Item
    </button>


    {/* Allow the user to update the shipping address */}
    <div>

        <h4>Shipping Address:</h4>

        <input type="text" placeholder="Street"
            value={cart.shippingAddress.street}
            onChange={(e) => actions.updateShippingAddress({ shippingAddress: { ...cart.shippingAddress, street: e.target.value } })}
        />

        <input type="text" placeholder="City"
            value={cart.shippingAddress.city}
            onChange={(e) => actions.updateShippingAddress({ shippingAddress: { ...cart.shippingAddress, city: e.target.value } })}
        />

    </div>


</div>
Enter fullscreen mode Exit fullscreen mode

 

使用 useImmer 优于 useReducer

 

在本节中,我们已经探讨了 useImmer 相对于 useReducer 的两个优势(可变性和可读性)Create Actions。然而,还有一个更重要的优势值得讨论,我们将在本节中介绍。

 

⚡️ 灵活性:使用 useImmer,可以在定义的操作之外更新状态对象。这在 useReducer 中是无法实现的,并且在需要更灵活地更新状态的某些情况下尤其有用。

 
以下是如何useImmer在不定义新操作的情况下清空购物车的示例:

<button onClick={() => updateCart(initialState)}>Clear Cart</button>
Enter fullscreen mode Exit fullscreen mode

此按钮组件会将cart状态重置回其初始状态,并清空购物车中的所有商品。使用useImmer,我们可以以这种临时方式直接更新状态对象,而无需定义任何操作。这在 中是不可能的useReducer,因为所有状态更新都必须通过已定义好的操作来调度。

 

完整代码

 

// import useImmer
import { useImmer } from 'use-immer';


// functional component
export default function Cart() {

    // Define the initial state of the cart
    const initialState = {
        items: [],
        shippingAddress: {
            street: '',
            city: ''
        }
    };

    // Call the useImmer hook to create a cart state 
    const [cart, updateCart] = useImmer(initialState);

    // Define a set of actions that can be used to update the cart state
    const actions = {

        // Add an item to the cart
        addItemToCart: (payload) => {

            const { item } = payload

            updateCart(draft => {
                draft.items.push(item)
            });
        },

        // Remove an item from the cart
        removeItemFromCart: (payload) => {

            const { itemIndex } = payload

            updateCart(draft => {
                draft.items.splice(itemIndex, 1)
            })
        },

        // Update the shipping address of the cart
        updateShippingAddress: (payload) => {

            const { shippingAddress } = payload

            updateCart(draft => {
                draft.shippingAddress = shippingAddress
            })
        }
    }


    // Render the cart UI
    return (

        <div>

            {/* Displaying the changes of the 'cart' state */}
            <p>Cart State: {JSON.stringify(cart)}</p>

            {/* Display a list of items in the cart */}
            <ul>
                {cart.items.map((item, index) => (
                    <li key={index}>
                        {item.name} - ${item.price}

                        {/* Call the removeItemFromCart action when the remove button is clicked */}
                        <button onClick={() => actions.removeItemFromCart({ itemIndex: index })}>Remove</button>
                    </li>
                ))}
            </ul>

            {/* Call the addItemToCart action when the add item button is clicked */}
            <button onClick={() => actions.addItemToCart({ item: { name: 'Product', price: 9.99 } })}>
                Add Item
            </button>


            {/* Allow the user to update the shipping address */}
            <div>

                <h4>Shipping Address:</h4>

                <input type="text" placeholder="Street"
                    value={cart.shippingAddress.street}
                    onChange={(e) => actions.updateShippingAddress({ shippingAddress: { ...cart.shippingAddress, street: e.target.value } })}
                />

                <input type="text" placeholder="City"
                    value={cart.shippingAddress.city}
                    onChange={(e) => actions.updateShippingAddress({ shippingAddress: { ...cart.shippingAddress, city: e.target.value } })}
                />

            </div>

            {/* Call the updateCart function with the initial state to clear the cart */}
            <button onClick={() => updateCart(initialState)}>Clear Cart</button>

        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

 

⬆️ 返回目录

 

我们什么时候应该使用 useImmer?

 

🧨 用于useImmer本地状态管理和创建可重用组件

当管理本地状态或创建需要状态管理的可重用组件时,useImmer是一个合适的选择。

 

🧨 我们可以使用useImmer全局状态管理,但还有其他更好的选择

我们可以使用和useImmer与其他组件共享 由 创建的状态。因此,我们可以使用来创建全局状态,但是我们应该使用它来进行全局状态管理吗?createContextuseContextuseImmer

当谈到全局状态管理时,像redux toolkitzustand这样的库更适合。

 

🧨 尽管其他库可能更适合全局状态管理,但 useImmer 是本地状态管理和创建可重用组件的更好选择

redux像和 这样的库zustand是为全局状态管理而创建的。如果我们用它们来创建可复用的组件:

  • 由于多个组件可以修改相同的状态,因此状态将受到污染,从而导致意外行为和错误。

另一方面,如果我们使用useImmer可重用组件:

  • 组件的每个实例都有其独立的状态,与同一组件的其他实例相互独立。这确保了一个实例的状态不会影响另一个实例的状态。

  • 由于状态包含在组件内,因此无需担心状态在应用程序的其他地方被修改。

 

🧨 在其他组件之间共享 useImmer 创建状态的方法

对于全局状态

  • 我们可以使用createContextuseContext在其他组件之间共享状态。

本地状态和可重用组件:

  • 假设我们在组件中使用本地状态或创建一个可复用组件。该组件变得越来越大,因此我们将其拆分为多个子组件。现在,在父组件中,我们将使用 创建状态useImmer。然后,我们将通过 props 与子组件共享状态。

 

⬆️ 返回目录

 

就这样。😃 感谢阅读。🎉

鏂囩珷鏉ユ簮锛�https://dev.to/rasaf_ibrahim/ditching-usestate-and-usereducer-why-useimmer-is-the-better-option-11hk
PREV
像专业人士一样编写用于密码验证的正则表达式模式
NEXT
开发适用于 Bootstrap 5 的免费低代码仪表板构建器