【问题标题】:Persist localStorage with useReducer使用 useReducer 持久化 localStorage
【发布时间】:2021-10-01 01:50:25
【问题描述】:

我有一个使用useState 的迷你购物车应用程序。我现在想将应用程序的状态重构为由useReducer 管理,并继续使用localStorage 持久化数据。

我很难弄清楚如何重构,因为涉及到许多移动的部分。如何重构 addToCartHandler 中的逻辑以在 ADD_TO_CART 案例中使用?从那里,我相信我能够找出cartReducer 中其他案例的模式。谢谢。

https://codesandbox.io/s/goofy-water-pb903?file=/src/App.js

【问题讨论】:

    标签: reactjs react-hooks local-storage use-reducer


    【解决方案1】:

    使用 Context API 管理购物车状态

    我首先将您的购物车状态和持久性隔离到本地存储到反应上下文提供程序。上下文可以为应用程序的其余部分提供购物车状态和操作调度程序,并在使用效果更新状态时将状态持久化到 localStorage。这将所有状态管理与应用程序分离,应用程序只需要使用上下文来访问购物车状态并调度操作来更新它。

    import React, { createContext, useEffect, useReducer } from "react";
    import { cartReducer, initializer } from "../cartReducer";
    
    export const CartContext = createContext();
    
    export const CartProvider = ({ children }) => {
      const [cart, dispatch] = useReducer(cartReducer, [], initializer);
    
      useEffect(() => {
        localStorage.setItem("localCart", JSON.stringify(cart));
      }, [cart]);
    
      return (
        <CartContext.Provider
          value={{
            cart,
            dispatch
          }}
        >
          {children}
        </CartContext.Provider>
      );
    };
    

    将应用程序包装在 index.js 中的 CartProvider

    <CartProvider>
      <App />
    </CartProvider>
    

    完成应用程序的其余部分

    cartReducer 中细化reducer,并导出初始化函数和action creators。

    const initialState = [];
    
    export const initializer = (initialValue = initialState) =>
      JSON.parse(localStorage.getItem("localCart")) || initialValue;
    
    export const cartReducer = (state, action) => {
      switch (action.type) {
        case "ADD_TO_CART":
          return state.find((item) => item.name === action.item.name)
            ? state.map((item) =>
                item.name === action.item.name
                  ? {
                      ...item,
                      quantity: item.quantity + 1
                    }
                  : item
              )
            : [...state, { ...action.item, quantity: 1 }];
    
        case "REMOVE_FROM_CART":
          return state.filter((item) => item.name !== action.item.name);
    
        case "DECREMENT_QUANTITY":
          // if quantity is 1 remove from cart, otherwise decrement quantity
          return state.find((item) => item.name === action.item.name)?.quantity ===
            1
            ? state.filter((item) => item.name !== action.item.name)
            : state.map((item) =>
                item.name === action.item.name
                  ? {
                      ...item,
                      quantity: item.quantity - 1
                    }
                  : item
              );
    
        case "CLEAR_CART":
          return initialState;
    
        default:
          return state;
      }
    };
    
    export const addToCart = (item) => ({
      type: "ADD_TO_CART",
      item
    });
    
    export const decrementItemQuantity = (item) => ({
      type: "DECREMENT_QUANTITY",
      item
    });
    
    export const removeFromCart = (item) => ({
      type: "REMOVE_FROM_CART",
      item
    });
    
    export const clearCart = () => ({
      type: "CLEAR_CART"
    });
    

    Product.js 中,通过useContext 挂钩获取购物车上下文并调度addToCart 操作

    import React, { useContext, useState } from "react";
    import { CartContext } from "../CartProvider";
    import { addToCart } from "../cartReducer";
    
    const Item = () => {
      const { dispatch } = useContext(CartContext);
    
      ...
    
      const addToCartHandler = (product) => {
        dispatch(addToCart(product));
      };
    
      ...
    
      return (
        ...
      );
    };
    

    CartItem.js 获取并使用购物车上下文来调度操作以减少数量或移除商品。

    import React, { useContext } from "react";
    import { CartContext } from "../CartProvider";
    import { decrementItemQuantity, removeFromCart } from "../cartReducer";
    
    const CartItem = () => {
      const { cart, dispatch } = useContext(CartContext);
    
      const removeFromCartHandler = (itemToRemove) =>
        dispatch(removeFromCart(itemToRemove));
    
      const decrementQuantity = (item) => dispatch(decrementItemQuantity(item));
    
      return (
        <>
          {cart.map((item, idx) => (
            <div className="cartItem" key={idx}>
              <h3>{item.name}</h3>
              <h5>
                Quantity: {item.quantity}{" "}
                <span>
                  <button type="button" onClick={() => decrementQuantity(item)}>
                    <i>Decrement</i>
                  </button>
                </span>
              </h5>
              <h5>Cost: {item.cost} </h5>
              <button onClick={() => removeFromCartHandler(item)}>Remove</button>
            </div>
          ))}
        </>
      );
    };
    

    App.js 通过上下文挂钩获取购物车状态和调度程序,并更新商品总数和价格逻辑以考虑商品数量。

    import { CartContext } from "./CartProvider";
    import { clearCart } from "./cartReducer";
    
    export default function App() {
      const { cart, dispatch } = useContext(CartContext);
    
      const clearCartHandler = () => {
        dispatch(clearCart());
      };
    
      const { items, total } = cart.reduce(
        ({ items, total }, { cost, quantity }) => ({
          items: items + quantity,
          total: total + quantity * cost
        }),
        { items: 0, total: 0 }
      );
    
      return (
        <div className="App">
          <h1>Emoji Store</h1>
          <div className="products">
            <Product />
          </div>
          <div className="cart">
            <CartItem />
          </div>
          <h3>
            Items in Cart: {items} | Total Cost: ${total.toFixed(2)}
          </h3>
          <button onClick={clearCartHandler}>Clear Cart</button>
        </div>
      );
    }
    

    【讨论】:

    • 哇,非常感谢您让应用程序更进一步!感谢您一直以来的帮助。
    • @ln09nv2 是的,不客气。当 Zacharey 发布他们的答案时,我对提议的更改进行了大约 75% 的更改,我想确保我带来了足够不同的东西,值得回答。
    • 我接受了您的回答,因为它是更理想的解决方案,而且您以前帮助过我!
    【解决方案2】:

    这是我的工作。我为 cartReducer 添加了所有案例,因为我玩得很开心。

    如果您想自己完成它,这里是第一个设置使用 localStorage 来保存项目值的情况。

    我正在做的概述是:使用switch case在reducer中设置新状态,然后每次购物车通过效果更改时将localStorage状态设置为新值。

    产品中的逻辑只是替换为一个简单的动作调度。因为逻辑在减速器中。您可能可以简化 ADD_TO_CART 案例中的逻辑,但这会以不可变的方式处理所有内容。使用诸如 immer 之类的东西会大大简化逻辑。

    const storageKey = "localCart";
    const cartReducer = (state, action) => {
      switch (action.type) {
        case "ADD_TO_CART": {
          const product = action.payload;
          let index = state.findIndex((item) => product.name === item.name);
          if (index >= 0) {
            const newState = [...state];
            newState.splice(index, 1, {
              ...state[index],
              quantity: state[index].quantity + 1
            });
            return newState
          } else {
            return [...state, { ...product, quantity: 1 }];
          }
        }
        default:
          throw new Error();
      }
    };
    

    App组件中使用:

      const [cart, cartDispatch] = useReducer(
        cartReducer,
        [],
        // So we only have to pull from localStorage one time - Less file IO
        (initial) => JSON.parse(localStorage.getItem(storageKey)) || initial
      );
      useEffect(() => {
        // This is a side-effect and belongs in an effect
        localStorage.setItem(storageKey, JSON.stringify(cart));
      }, [cart]);
    

    Product组件中使用:

      const addToCartHandler = (product) => {
        dispatch({ type: "ADD_TO_CART", payload: product });
      };
    
    

    完整的工作CodeSandbox

    【讨论】:

    • 非常感谢您解释重构的步骤!我也感谢注释代码以帮助我理解。
    猜你喜欢
    • 2012-06-27
    • 2021-05-20
    • 2020-10-11
    • 2015-05-28
    • 2014-10-26
    • 1970-01-01
    • 1970-01-01
    • 2018-05-21
    相关资源
    最近更新 更多