You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React-Redux复用带参Action时数据重复的问题解决

React-Redux 多品牌商品展示数据覆盖问题解决

问题场景

开发虚拟商城时,需要实现全商品列表+多个品牌分类展示区。编写了带mark参数的异步Action getProductsByMark,但多次调用(传入Nike、Fila、Adidas等参数)时,所有展示区都显示最后一次请求的Adidas数据。目标是复用同一Action,不为每个品牌单独编写控制器。

原Action代码

export const getProductsByMark = mark => async dispatch => {
  try {
    dispatch({type: actionTypes.GET_PRODUCTSBYMARK_REQUEST})

    const {data} = await Api.getRequest(`/api/products/mark/${mark}`)    
    dispatch({
      type: actionTypes.GET_PRODUCTSBYMARK_SUCCESS,
      payload: JSON.parse(data),      
    })    
  } catch (error) {
    dispatch({
      type: actionTypes.GET_PRODUCTSBYMARK_FAIL,
      payload:
        error.response && error.response.data.message
          ? error.response.data.message
          : error.message,
    })
  }
}

原组件代码(Nike示例)

export const SetNikeProducts = () => {         
  const nikeDispatch = useDispatch() 
  const getProductsMark = useSelector(state => state.getProductsByMark)
  const { products: productsNike, loading: loadingNike, error: erroNike } = getProductsMark;    

  useEffect(() => {    
    nikeDispatch(listProductsMark('Nike'))
  }, [])

  return (
  productsNike.length != 0 || productsNike != undefined ?
    <div>
      <br></br>
      <h2 className="homescreen__title">Nike</h2>
      <div className="homescreen__products">
        {loadingNike ? (
          <h2>Loading...</h2>
        ) : erroNike ? (
          <h2>{erroNike}</h2>
        ) : (
          productsNike.map(product => (
            <Product
              key={product._id}
              name={product.name}
              description={product.description}
              mark={product.mark}
              price={product.price}
              discount={product.discount}
              imageUrl={product.imageUrl}
              productId={product._id}
            />
          ))
        )}
      </div>
    </div>
    
    :
    <div>Deu ruim</div>
  )
}

问题根源

当前Redux的getProductsByMark reducer里,所有品牌的请求结果都存在同一个products字段中。每次新请求成功后,都会覆盖之前的数据,导致所有组件读取的都是最后一次请求的结果。

解决方案

1. 修改Action,携带品牌标识

在dispatch的action中加入mark参数,让reducer能区分不同品牌的数据:

export const getProductsByMark = mark => async dispatch => {
  try {
    dispatch({
      type: actionTypes.GET_PRODUCTSBYMARK_REQUEST,
      payload: mark // 新增品牌标识
    })

    const {data} = await Api.getRequest(`/api/products/mark/${mark}`)    
    dispatch({
      type: actionTypes.GET_PRODUCTSBYMARK_SUCCESS,
      payload: {
        mark, // 携带品牌信息
        products: JSON.parse(data)
      },      
    })    
  } catch (error) {
    dispatch({
      type: actionTypes.GET_PRODUCTSBYMARK_FAIL,
      payload: {
        mark,
        error: error.response && error.response.data.message
          ? error.response.data.message
          : error.message,
      },
    })
  }
}

2. 调整Reducer结构,按品牌存储数据

把原来单一的products、loading、error改成按品牌键值对存储,每个品牌独立维护自己的状态:

const initialState = {
  byMark: {} // 结构示例:{ Nike: { products: [], loading: false, error: null }, Fila: {...} }
}

export const productsByMarkReducer = (state = initialState, action) => {
  switch(action.type) {
    case actionTypes.GET_PRODUCTSBYMARK_REQUEST:
      return {
        ...state,
        byMark: {
          ...state.byMark,
          [action.payload]: {
            ...state.byMark[action.payload],
            loading: true,
            error: null
          }
        }
      }
    case actionTypes.GET_PRODUCTSBYMARK_SUCCESS:
      const { mark, products } = action.payload
      return {
        ...state,
        byMark: {
          ...state.byMark,
          [mark]: {
            products,
            loading: false,
            error: null
          }
        }
      }
    case actionTypes.GET_PRODUCTSBYMARK_FAIL:
      const { mark: failMark, error } = action.payload
      return {
        ...state,
        byMark: {
          ...state.byMark,
          [failMark]: {
            ...state.byMark[failMark],
            loading: false,
            error
          }
        }
      }
    default:
      return state
  }
}

3. 更新组件,读取对应品牌的状态

以Nike组件为例,调整useSelector逻辑,精准读取对应品牌的状态:

export const SetNikeProducts = () => {         
  const dispatch = useDispatch() 
  // 读取Nike对应的状态,没有则用默认值
  const nikeState = useSelector(state => state.productsByMark.byMark.Nike || { products: [], loading: false, error: null })
  const { products: productsNike, loading: loadingNike, error: erroNike } = nikeState;    

  useEffect(() => {    
    dispatch(getProductsByMark('Nike'))
  }, [dispatch])

  return (
    productsNike?.length ? (
      <div>
        <br></br>
        <h2 className="homescreen__title">Nike</h2>
        <div className="homescreen__products">
          {loadingNike ? (
            <h2>Loading...</h2>
          ) : erroNike ? (
            <h2>{erroNike}</h2>
          ) : (
            productsNike.map(product => (
              <Product
                key={product._id}
                name={product.name}
                description={product.description}
                mark={product.mark}
                price={product.price}
                discount={product.discount}
                imageUrl={product.imageUrl}
                productId={product._id}
              />
            ))
          )}
        </div>
      </div>
    ) : (
      erroNike ? <h2>{erroNike}</h2> : <div>Deu ruim</div>
    )
  )
}

4. 封装通用品牌组件(可选优化)

为了避免重复编写类似组件,封装一个通用组件,传入品牌名即可复用:

export const BrandProducts = ({ brandName }) => {         
  const dispatch = useDispatch() 
  const brandState = useSelector(state => state.productsByMark.byMark[brandName] || { products: [], loading: false, error: null })
  const { products, loading, error } = brandState;    

  useEffect(() => {    
    dispatch(getProductsByMark(brandName))
  }, [dispatch, brandName])

  if (loading) return <h2>Loading...</h2>
  if (error) return <h2>{error}</h2>
  if (!products?.length) return <div>Deu ruim</div>

  return (
    <div>
      <br></br>
      <h2 className="homescreen__title">{brandName}</h2>
      <div className="homescreen__products">
        {products.map(product => (
          <Product
            key={product._id}
            name={product.name}
            description={product.description}
            mark={product.mark}
            price={product.price}
            discount={product.discount}
            imageUrl={product.imageUrl}
            productId={product._id}
          />
        ))}
      </div>
    </div>
  )
}

使用时直接传入品牌名:

<BrandProducts brandName="Nike" />
<BrandProducts brandName="Fila" />
<BrandProducts brandName="Adidas" />

内容的提问来源于stack exchange,提问作者Pedro Elias

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 06:45:33