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

React同商品页加购不同规格时已入车商品属性异常问题

问题复现

React电商项目商品详情页(PDP)加购逻辑异常:用户停留在同一PDP页面操作时,先选择耐克T恤规格为color:black、size:S加购,此时购物车显示1件对应规格商品;不跳转页面,直接重新选择同一款T恤规格为color:white、size:M再次加购,首次加入购物车的商品属性会被同步篡改为第二次选择的规格,最终购物车内出现2件属性完全一致的商品,不符合业务预期。
涉及问题代码如下:

class PDP extends Component {
  state = {
    currentProduct: this.props.location.state,
    selectState: [],
    testObj: {},
  } 
  render() {
    let product = this.state.currentProduct;
    let handleChange = (evt,attribute) => {
      const test = Object.assign({}, product);
      this.state.testObj[attribute] = evt.target.value;
      this.state.selectState.push(this.state.testObj);
      let selectedAttributes = [...new Set(this.state.selectState)];
      test.selectedAttributes = selectedAttributes;
      this.setState((state) => ({
        currentProduct: test
      }))
    } 

    <button className="addToCart" onClick={() => this.props.data.addProduct(this.state.currentProduct)}>ADD TO CART</button>
  }

  addProduct = (product) => {
    this.setState((state) => ({
      cart: state.cart.concat(product),
    }))
  } 
}
问题根因
  • 直接修改state引用值,违反React不可变数据原则:handleChange方法中直接修改this.state.testObj的属性、直接调用push操作this.state.selectState数组,全程没有创建新的对象/数组副本。selectState中存入的全是指向同一个testObj的引用,后续修改testObj属性时,之前存入数组的所有项都会同步变更。
  • 浅拷贝未切断内存引用关联:Object.assign({}, product)仅能实现对象第一层的浅拷贝,对象内部的引用类型属性依然和原对象共用内存地址。加购时直接把state中currentProduct的引用传入addProduct,concat存入购物车数组的也是同一个对象的内存地址,后续页面上修改currentProduct属性时,购物车中已经存储的商品对象会被同步篡改。
  • 属性去重逻辑完全无效:Set结构判断引用类型是否重复时,比对的是内存地址而非对象实际内容,就算selectState里存了多个指向同一个testObj的项,[...new Set(this.state.selectState)]也无法实现属性去重效果。
修复方案
  1. 移除state中全局复用的testObj变量,每次选择规格时生成全新的对象副本,所有state修改操作都通过生成新值的方式完成,禁止直接赋值修改原state的引用类型属性
  2. 加购时对传入的商品对象做深拷贝,彻底切断购物车商品项和当前页面state的引用关联,避免后续页面操作篡改已加购的商品数据
  3. 移除无效的Set去重逻辑,选中属性直接以键值对形式存储,不需要维护冗余的selectState数组

修复后的核心参考代码:

class PDP extends Component {
  state = {
    currentProduct: this.props.location.state,
  }

  // 把事件处理函数从render中移出,避免每次渲染生成新函数
  handleChange = (evt, attribute) => {
    // 生成当前商品的深拷贝副本,不直接修改原state
    const newProduct = structuredClone(this.state.currentProduct);
    // 初始化选中属性对象
    if (!newProduct.selectedAttributes) newProduct.selectedAttributes = {};
    newProduct.selectedAttributes[attribute] = evt.target.value;

    this.setState({
      currentProduct: newProduct
    })
  }

  addProduct = (product) => {
    // 加购时做深拷贝,彻底断开和页面state的引用关联
    const cartItem = structuredClone(product);
    this.setState((state) => ({
      cart: state.cart.concat(cartItem),
    }))
  }

  render() {
    const { currentProduct: product } = this.state;
    return (
      <button 
        className="addToCart" 
        onClick={() => this.props.data.addProduct(product)}
      >
        ADD TO CART
      </button>
    )
  }
}

提示:上述代码用了原生structuredClone做深拷贝,兼容现代浏览器环境;如果需要兼容低版本浏览器,或商品对象包含函数、特殊实例等无法被结构化克隆的值,可以替换为对应场景可用的深拷贝实现。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 04:09:14