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
相关产品推荐
相关产品推荐

