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

如何用Redux结合数组单独管理每个商品的点赞状态?

问题描述

我尝试使用Redux和map函数实现like功能,以便在其他组件中使用状态。选择Redux而非useState([])是因为认为它更适合状态管理。目前点击按钮时所有商品的状态会同步变化,因为它们共享同一状态。我考虑将状态存入数组来解决该问题,想请教如何在Redux中添加状态以单独管理每个商品的点赞状态,以及这种方式是否正确。

以下是Clothes组件的代码:

import React from "react";
import styled from "styled-components";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faHeart } from "@fortawesome/free-solid-svg-icons";
import { faHome } from "@fortawesome/free-solid-svg-icons";
import { useDispatch, useSelector } from "react-redux";

const array = [
  {
    id: "1",
    name: "vest",
    img:
      "https://shopimg.kakaofriendsgolf.com/live/images/2022/9/7/10/918997_1662515279620.png",
    price: "10000"
  },
  {
    id: "2",
    name: "shirts",
    img:
      "https://shopimg.kakaofriendsgolf.com/live/images/2022/8/23/18/551886_1661246483199.png",
    price: "12000"
  },
  {
    id: "3",
    name: "pants",
    img:
      " https://shopimg.kakaofriendsgolf.com/live/images/2022/8/22/18/18783_1661159105201.png",
    price: "15000"
  }
];

export default function Clothes() {
  const isClick = useSelector((state) => state.CheckLike.isClick);

  const dispatch = useDispatch();

  // change Icon
  const setHide = (e) => {
    dispatch({ type: "False" });
  };

  const setShow = (e) => {
    dispatch({ type: "True" });
  };

  return (
    <Wrap>
      <div className="productCard">
        {array.map((content, idx) => {
          return (
            <div key={idx} className="productCard__wrap">
              <img src={content.img} className="productCard__img" />
              <div className="productCard__name">
                <div>
                  <h3 className="productCard__title">{content.name}</h3>
                  <div className="productCard__priceWrap">
                    {content.price
                      .toString()
                      .replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
                  </div>
                </div>
                <div className="productCard__likeWrap">
                  {/* show heart and house icon according to state */}
                  {isClick ? (
                    <div
                      onClick={() => {
                        setHide();
                      }}
                    >
                      <FontAwesomeIcon icon={faHeart} />
                    </div>
                  ) : (
                    <div
                      onClick={() => {
                        setShow();
                      }}
                    >
                      <FontAwesomeIcon icon={faHome} />
                    </div>
                  )}
                </div>
              </div>
            </div>
          );
        })}
      </div>
    </Wrap>
  );
}

const Wrap = styled.div`
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  overflow: scroll;

  .productCard {
    display: flex;
    position: absolute;
    top: calc(50vh - 120px);
    left: calc(50vw - 180px);
    width: "228px";
    height: "351px";
    padding-right: 8px;
    padding-left: 8px;
    border: solid 1px black;
  }

  .productCard__wrap {
    border: 1px solid grey;
    line-height: 1.428571429;
    background-color: #fff;
  }

  .productCard__img {
    width: 228px;
    height: 228px;
  }

  .productCard__name {
    padding-bottom: 30px;
    position: relative;
    padding: 8px 8px 0 8px;
    min-height: 118px;
    text-align: left;
  }

  .productCard__title {
    font-size: 18px;
    margin-top: 0;
    margin-bottom: 2px;
    padding-right: 37px;
    line-height: 1.3;
    margin-block-start: 1em;
    margin-block-end: 1em;
  }

  .productCard__priceWrap {
    font-size: 18px;
    margin-top: 5px;
    vertical-align: middle;
    margin: 0 0 9px;
    margin-block-start: 1em;
    margin-block-end: 1em;
    text-align: left;
  }

  .productCard__likeWrap {
    position: absolute;
    top: -5px;
    right: 0;
    padding: 0;
    height: 40px;
    line-height: 37px;
    white-space: normal;
    display: inline-block;
    margin-bottom: 0;
    font-weight: normal;
    text-align: center;
  }
`;
解决方案

你的思路是对的——用数组或更高效的对象映射来存储每个商品的点赞状态,而非单个布尔值,这样就能单独控制每个商品的点赞状态。具体实现步骤如下:

1. 修改Redux状态结构

把原来单一的isClick布尔值,改成以商品ID为键、布尔值为值的对象(比数组查询更新效率更高):

// 初始状态示例
const initialState = {
  likedIds: {} // 格式示例:{ "1": true, "2": false }
};

2. 更新Reducer逻辑

修改Reducer,处理单个商品的点赞/取消点赞动作,接收商品ID作为payload:

const checkLikeReducer = (state = initialState, action) => {
  switch (action.type) {
    case "TOGGLE_LIKE":
      const productId = action.payload;
      return {
        ...state,
        likedIds: {
          ...state.likedIds,
          [productId]: !state.likedIds[productId] // 切换该商品的点赞状态
        }
      };
    default:
      return state;
  }
};

3. 调整Clothes组件逻辑

  • 不再使用全局的isClick,而是根据当前商品ID从Redux状态中获取对应点赞状态
  • 点击时dispatch带商品ID的动作,而非统一的True/False指令

修改后的核心组件代码:

export default function Clothes() {
  const likedIds = useSelector((state) => state.CheckLike.likedIds);
  const dispatch = useDispatch();

  // 切换单个商品的点赞状态
  const toggleLike = (productId) => {
    dispatch({ type: "TOGGLE_LIKE", payload: productId });
  };

  return (
    <Wrap>
      <div className="productCard">
        {array.map((content) => {
          // 根据商品ID判断是否已点赞
          const isLiked = likedIds[content.id] || false;
          return (
            <div key={content.id} className="productCard__wrap">
              <img src={content.img} className="productCard__img" />
              <div className="productCard__name">
                <div>
                  <h3 className="productCard__title">{content.name}</h3>
                  <div className="productCard__priceWrap">
                    {content.price
                      .toString()
                      .replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
                  </div>
                </div>
                <div className="productCard__likeWrap">
                  <div onClick={() => toggleLike(content.id)}>
                    <FontAwesomeIcon icon={isLiked ? faHeart : faHome} />
                  </div>
                </div>
              </div>
            </div>
          );
        })}
      </div>
    </Wrap>
  );
}

方案合理性说明

  • 每个商品的点赞状态独立存储,不会出现同步变化的问题
  • Redux状态结构清晰,其他组件可以轻松获取任意商品的点赞状态
  • 用对象存储likedIds,更新和查询的时间复杂度均为O(1),比数组遍历更高效
  • 改用商品唯一ID作为列表key,符合React列表渲染的最佳实践,避免组件复用导致的状态混乱

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 04:55:28