放弃 useState 和 useReducer:为什么 useImmer 是更好的选择
在 React 中,useState
和useReducer
通常用于管理状态。虽然useImmer
是 的流行替代方案useState
,但在本文中,我们将探讨如何将其作为 的更简单、更有效的替代方案useReducer
。
目录
如果你不熟悉 useImmer
useImmer
是一个自定义的 React Hook。它与 类似,useState
但具有一些独特的优势,尤其是在管理复杂状态时。使用useImmer
,您可以像常规 JavaScript 一样直接更新状态,就像它是直接可变的一样。这是可能的,因为在后台useImmer
使用该Immer
库创建了状态的新的不可变副本。
要使用useImmer
,首先,您需要通过在终端中运行以下命令来安装它:
npm install immer use-immer
一个简单的例子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>
)
}
为什么我们应该使用 useImmer 而不是 useReducer
我们将探索一个useImmer
遵循该useReducer
模式的 示例。我们将创建状态和动作,并以与 类似的方式使用它们useReducer
,但 useImmer 具有更高的可读性、灵活性和可变性。通过这个例子,我们将了解为什么useImmer
应该比 更受重视useReducer
。
导入 useImmer 并创建函数组件
// import useImmer
import { useImmer } from 'use-immer';
// functional component
function Cart() {
// ...
}
定义初始状态
使用 时useReducer
,通常的做法是在继续操作之前定义初始状态。同样,我们将在useImmer
hook 中使用这种方法。为此,我们将创建一个稍微复杂一些的状态,类似于您通常使用 定义的状态useReducer
。
// initial state
const initialState = {
items: [],
shippingAddress: {
street: '',
city: ''
}
}
创建国家
我们已经定义了初始状态。现在,我们可以使用钩子来创建状态useImmer
。
// Creating the state
const [cart, updateCart] = useImmer(initialState)
该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
})
}
}
使用上述方法定义动作有两个优点useReducer
:
⚡️ 可变性:有了useImmer
,我们拥有了可变更新的强大功能,可以减少代码量,并使其更像 JavaScript。这与useReducer
需要更函数式编程方法和不可变更新的 形成了对比。通过在 中使用可变更新useImmer
,您可以用更少的代码行实现相同的结果,从而更易于编写和维护。
⚡️ 可读性:与 相比useReducer
,其中动作通常定义为switch
reducer 函数中的案例,而该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>
使用 useImmer 优于 useReducer
在本节中,我们已经探讨了 useImmer 相对于 useReducer 的两个优势(可变性和可读性)Create Actions
。然而,还有一个更重要的优势值得讨论,我们将在本节中介绍。
⚡️ 灵活性:使用 useImmer,可以在定义的操作之外更新状态对象。这在 useReducer 中是无法实现的,并且在需要更灵活地更新状态的某些情况下尤其有用。
以下是如何useImmer
在不定义新操作的情况下清空购物车的示例:
<button onClick={() => updateCart(initialState)}>Clear Cart</button>
此按钮组件会将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>
)
}
我们什么时候应该使用 useImmer?
🧨 用于useImmer
本地状态管理和创建可重用组件
当管理本地状态或创建需要状态管理的可重用组件时,
useImmer
是一个合适的选择。
🧨 我们可以使用useImmer
全局状态管理,但还有其他更好的选择
我们可以使用和
useImmer
与其他组件共享 由 创建的状态。因此,我们可以使用来创建全局状态,但是我们应该使用它来进行全局状态管理吗?createContext
useContext
useImmer
当谈到全局状态管理时,像
redux toolkit
或zustand
这样的库更适合。
🧨 尽管其他库可能更适合全局状态管理,但 useImmer 是本地状态管理和创建可重用组件的更好选择
redux
像和 这样的库zustand
是为全局状态管理而创建的。如果我们用它们来创建可复用的组件:
- 由于多个组件可以修改相同的状态,因此状态将受到污染,从而导致意外行为和错误。
另一方面,如果我们使用
useImmer
可重用组件:
组件的每个实例都有其独立的状态,与同一组件的其他实例相互独立。这确保了一个实例的状态不会影响另一个实例的状态。
由于状态包含在组件内,因此无需担心状态在应用程序的其他地方被修改。
🧨 在其他组件之间共享 useImmer 创建状态的方法
对于全局状态
- 我们可以使用
createContext
和useContext
在其他组件之间共享状态。本地状态和可重用组件:
- 假设我们在组件中使用本地状态或创建一个可复用组件。该组件变得越来越大,因此我们将其拆分为多个子组件。现在,在父组件中,我们将使用 创建状态
useImmer
。然后,我们将通过 props 与子组件共享状态。
就这样。😃 感谢阅读。🎉
鏂囩珷鏉ユ簮锛�https://dev.to/rasaf_ibrahim/ditching-usestate-and-usereducer-why-useimmer-is-the-better-option-11hk