MST
星途 面试题库

面试题:React useReducer钩子在复杂组件状态管理中的应用

假设有一个电商购物车组件,需要管理商品列表、商品数量增减、总价计算以及促销活动(如满减)等复杂状态。请使用useReducer实现这个购物车组件的状态管理,并说明如何利用action的不同类型来处理各种状态变化,以及如何优化性能,避免不必要的重新渲染。
15.8万 热度难度
前端开发React

知识考点

AI 面试

面试题答案

一键面试

1. 使用 useReducer 实现购物车组件状态管理

首先,我们定义购物车状态的结构和 action 的类型。

import React, { useReducer } from'react';

// 定义购物车商品类型
type CartItem = {
  id: number;
  name: string;
  price: number;
  quantity: number;
};

// 定义购物车状态类型
type CartState = {
  items: CartItem[];
  totalPrice: number;
  discount: number;
};

// 定义 action 类型
enum CartActionType {
  ADD_ITEM = 'ADD_ITEM',
  REMOVE_ITEM = 'REMOVE_ITEM',
  INCREMENT_QUANTITY = 'INCREMENT_QUANTITY',
  DECREMENT_QUANTITY = 'DECREMENT_QUANTITY',
  APPLY_DISCOUNT = 'APPLY_DISCOUNT'
}

// 定义 action 接口
type CartAction =
  | { type: CartActionType.ADD_ITEM; payload: CartItem }
  | { type: CartActionType.REMOVE_ITEM; payload: number }
  | { type: CartActionType.INCREMENT_QUANTITY; payload: number }
  | { type: CartActionType.DECREMENT_QUANTITY; payload: number }
  | { type: CartActionType.APPLY_DISCOUNT; payload: number };

// 初始化购物车状态
const initialCartState: CartState = {
  items: [],
  totalPrice: 0,
  discount: 0
};

// reducer 函数
const cartReducer = (state: CartState, action: CartAction): CartState => {
  switch (action.type) {
    case CartActionType.ADD_ITEM:
      return {
      ...state,
        items: [...state.items, action.payload],
        totalPrice: state.totalPrice + action.payload.price
      };
    case CartActionType.REMOVE_ITEM:
      const removedItem = state.items.find(item => item.id === action.payload);
      if (!removedItem) return state;
      return {
      ...state,
        items: state.items.filter(item => item.id!== action.payload),
        totalPrice: state.totalPrice - removedItem.price * removedItem.quantity
      };
    case CartActionType.INCREMENT_QUANTITY:
      return {
      ...state,
        items: state.items.map(item =>
          item.id === action.payload
          ? {...item, quantity: item.quantity + 1 }
           : item
        ),
        totalPrice: state.totalPrice + state.items.find(item => item.id === action.payload)?.price || 0
      };
    case CartActionType.DECREMENT_QUANTITY:
      return {
      ...state,
        items: state.items.map(item =>
          item.id === action.payload && item.quantity > 1
          ? {...item, quantity: item.quantity - 1 }
           : item
        ),
        totalPrice: state.totalPrice - (state.items.find(item => item.id === action.payload)?.price || 0)
      };
    case CartActionType.APPLY_DISCOUNT:
      return {
      ...state,
        discount: action.payload,
        totalPrice: state.totalPrice - action.payload
      };
    default:
      return state;
  }
};

const CartComponent = () => {
  const [cartState, dispatch] = useReducer(cartReducer, initialCartState);

  return (
    <div>
      <h1>购物车</h1>
      {/* 显示购物车商品列表、总价等信息 */}
    </div>
  );
};

export default CartComponent;

2. 利用 action 不同类型处理状态变化

  • ADD_ITEM: 当需要向购物车添加商品时,触发此 action。在 reducer 中,将新商品添加到 items 数组,并更新 totalPrice
  • REMOVE_ITEM: 移除购物车中的某个商品。reducer 过滤掉要移除的商品,并相应减少 totalPrice
  • INCREMENT_QUANTITY: 增加指定商品的数量。reducer 找到对应商品并增加其数量,同时更新 totalPrice
  • DECREMENT_QUANTITY: 减少指定商品的数量(但数量需大于1)。reducer 找到对应商品并减少其数量,同时更新 totalPrice
  • APPLY_DISCOUNT: 应用促销活动(如满减)。reducer 更新 discount 并从 totalPrice 中减去折扣金额。

3. 优化性能,避免不必要的重新渲染

  • 使用 React.memo: 对于购物车组件的子组件,如果其 props 没有变化,使用 React.memo 包裹可以避免不必要的重新渲染。例如,如果有一个展示单个商品信息的子组件,只要该商品的信息(如 namepricequantity)没有改变,就不会重新渲染。
const ItemComponent = React.memo(({ item }: { item: CartItem }) => {
  return (
    <div>
      <p>{item.name}</p>
      <p>价格: {item.price}</p>
      <p>数量: {item.quantity}</p>
    </div>
  );
});
  • 拆分状态: 如果购物车组件的某些状态变化频率较低,可以将其拆分成独立的状态管理。例如,促销活动的配置信息可能不经常变化,可以将其放在一个单独的 useStateuseReducer 中管理,这样当购物车商品列表变化时,不会触发与促销活动配置相关的子组件的重新渲染。
  • 使用 useCallbackuseMemo: 在传递给子组件的函数或计算值可能导致子组件重新渲染时,使用 useCallback 缓存函数,useMemo 缓存计算值。例如,如果有一个函数用于计算购物车中商品的总重量(假设商品有重量属性),并且这个函数传递给一个子组件,可以使用 useMemo 缓存计算结果,避免每次父组件渲染时都重新计算。
const totalWeight = useMemo(() => {
  return cartState.items.reduce((acc, item) => acc + item.weight * item.quantity, 0);
}, [cartState.items]);
const handleIncrement = useCallback((id: number) => {
  dispatch({ type: CartActionType.INCREMENT_QUANTITY, payload: id });
}, []);