无法正确更新状态中的总值

Cannot properly update total value in state

我正在编写没有 redux 的简单购物车系统。

预期行为:更改产品数量时更新总金额

问题:

  1. 第一次渲染后总量为0
  2. 删除项目后总金额不变

我需要: 优雅的解决方案。不是硬编码。

const products = [
  {
   id: "1",
    title: "item 1",
    price: "2450",
    left: "14",
    quantity: "1"
  },
  {
   id: "2",
    title: "item 2",
    price: "2450",
    left: "178",
    quantity: "1"
  },
  {
   id: "3",
    title: "item 3",
    price: "2450",
    left: "1",
    quantity: "1"
  },
  {
   id: "4",
    title: "item 4",
    price: "2450",
    left: "12",
    quantity: "1"
  }
];

class App extends React.Component {
  constructor( props ) {
    super( props );
    this.state = {
      products: this.props.products,
      cart: this.props.products,
      totalAmount: 0
    };
    
    this.handleRemoveProduct = this.handleRemoveProduct.bind( this );
    this.handleChangeQuantity = this.handleChangeQuantity.bind( this );
    this.sumTotalAmount = this.sumTotalAmount.bind( this );
  }
  
  
  handleRemoveProduct( id ) {
    let cart = this.state.cart;
    cart = cart.filter( ( product ) => {
     return product.id != id;
    } );
    
    this.setState( {
     cart
    } );
    
    this.sumTotalAmount();
  }
  
  handleChangeQuantity( e, id ) {
    let cart = this.state.cart;
    cart = cart.map( ( product ) => {
     if (product.id == id ) {
       product.quantity = e.target.value;
      }
     
     return product;
    } );
    
    this.setState( {
     cart
    } );
    this.sumTotalAmount();
  }
  
  sumTotalAmount() {
   let cart = this.state.cart;
    let totalAmount = cart.map( ( product ) => {
     return Number(product.quantity) * Number(product.price);
    } ).reduce( ( total, current ) => total += current );
    
    this.setState( {
     totalAmount
    } );
  }

  render() {
    return(
      <div>
        <div className="cart">
          {
            this.state.cart.map( ( item, index ) => {
              return(
                <Product key={index} item={item}
                handleRemoveProduct={this.handleRemoveProduct}
                handleChangeQuantity={this.handleChangeQuantity}
                />
              )
            })
          }
        </div>
        <div className="cart__total">     
          Total amount - {this.state.totalAmount}
        </div>
      </div>
    )
  }
}

const Product = ( props ) => (
  <div className="cart__product">
    {props.item.title} <a href="#" onClick={() => props.handleRemoveProduct(props.item.id)}>Remove</a>
     <input type="number" name="quantity" min="1" max={props.item.left} value={props.item.quantity} onChange={(e) => props.handleChangeQuantity(e, props.item.id)}/>
  </div>
);

ReactDOM.render(<App products = {products} />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>

其实问题是你在setState之后调用了sum方法,setState是异步的,所以执行sum的时候state并没有改变,setState接收第二个参数,回调之后执行状态已更新,请参阅下面的文档 https://reactjs.org/docs/react-component.html#setstate

使 sumTotalAmount 基于 prevState 工作解决了问题

 sumTotalAmount() {   
        this.setState( (prevState, props) => { return { totalAmount : prevState.cart.map(( product ) => {
            return Number(product.quantity) * Number(product.price);
        } ).reduce( ( total, current ) => total += current )}
      })
  }

const products = [
  {
   id: "1",
    title: "item 1",
    price: "2450",
    left: "14",
    quantity: "1"
  },
  {
   id: "2",
    title: "item 2",
    price: "2450",
    left: "178",
    quantity: "1"
  },
  {
   id: "3",
    title: "item 3",
    price: "2450",
    left: "1",
    quantity: "1"
  },
  {
   id: "4",
    title: "item 4",
    price: "2450",
    left: "12",
    quantity: "1"
  }
];

class App extends React.Component {
  constructor( props ) {
    super( props );
    this.state = {
      products: this.props.products,
      cart: this.props.products,
      totalAmount: 0
    };
    
    this.handleRemoveProduct = this.handleRemoveProduct.bind( this );
    this.handleChangeQuantity = this.handleChangeQuantity.bind( this );
    this.sumTotalAmount = this.sumTotalAmount.bind( this );
  }
  
  
  handleRemoveProduct( id ) {
    let cart = this.state.cart;
    cart = cart.filter( ( product ) => {
     return product.id != id;
    } );
    
    this.setState( {
     cart
    } );
    
    this.sumTotalAmount();
  }
  
  handleChangeQuantity( e, id ) {
    let cart = this.state.cart;
    cart = cart.map( ( product ) => {
     if (product.id == id ) {
       product.quantity = e.target.value;
      }
     
     return product;
    } );
    
    this.setState( {
     cart
    } );
    this.sumTotalAmount();
  }
  
  sumTotalAmount() {
     
    this.setState( (prevState, props) => { return { totalAmount : prevState.cart.map(( product ) => {
     return Number(product.quantity) * Number(product.price);
    } ).reduce( ( total, current ) => total += current )}
  })
                  }

  render() {
    return(
      <div>
        <div className="cart">
          {
            this.state.cart.map( ( item, index ) => {
              return(
                <Product key={index} item={item}
                handleRemoveProduct={this.handleRemoveProduct}
                handleChangeQuantity={this.handleChangeQuantity}
                />
              )
            })
          }
        </div>
        <div className="cart__total">     
          Total amount - {this.state.totalAmount}
        </div>
      </div>
    )
  }
}

const Product = ( props ) => (
  <div className="cart__product">
    {props.item.title} <a href="#" onClick={() => props.handleRemoveProduct(props.item.id)}>Remove</a>
     <input type="number" name="quantity" min="1" max={props.item.left} value={props.item.quantity} onChange={(e) => props.handleChangeQuantity(e, props.item.id)}/>
  </div>
);

ReactDOM.render(<App products = {products} />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>

1.this.setState 是异步的,因此当您调用函数 sumTotalAmount 时状态不会得到更新,因此您将得到 所有项目的总和,即使您删除了一项。

2.Initially 您没有计算总和,因此它显示总和为 0。 因此,请尝试在 componentDidMount 中调用该函数 sumTotalAmount

Bruno 添加了一段代码来帮助解决问题,来自:

  Total amount - {this.state.totalAmount}

收件人:

  Total amount - {this.state.cart.map( ( product ) => {
    return Number(product.quantity) * Number(product.price)
 }).reduce( ( total, current ) => total += current )}

问题是双重的:

1) totalAmount 初始化为 0,直到您对购物车进行更改后,才会调用 sumTotalAmount 来更新它。

2) totalAmount 滞后,尤其是在最后一个 Remove 上,因为每次调用 sumTotalAmount 都依赖于状态,状态可能不是最新的(Bruno 提到的异步部分)。

我会将您的(更新的)购物车传递给 sumTotalAmount,并使用其输出来设置 totalAmount,无论是在构建还是在更改时:

constructor( props ) {
    super( props );

    this.handleRemoveProduct = this.handleRemoveProduct.bind( this );
    this.handleChangeQuantity = this.handleChangeQuantity.bind( this );
    this.sumTotalAmount = this.sumTotalAmount.bind( this );

    this.state = {
      products: this.props.products,
      cart: this.props.products,
      totalAmount: this.sumTotalAmount(this.props.products)
    };

}


handleRemoveProduct( id ) {
    let cart = this.state.cart;
    cart = cart.filter( ( product ) => {
        return product.id != id;
    } );

    this.setState( {
        cart: cart,
        totalAmount: this.sumTotalAmount(cart)
    } );

    //this.sumTotalAmount(cart);
}

// Make a similar change to handleChangeQuantity


sumTotalAmount(cart) {
    //let cart = this.state.cart;
    let totalAmount = cart.map( ( product ) => {
        return Number(product.quantity) * Number(product.price);
    } ).reduce( ( total, current ) => total += current );

    //this.setState( {
    //  totalAmount
    //} );
    return totalAmount;
}

setState 方法是异步的,所以 this.sumTotalAmount() 几乎什么都不做。 您至少有三种方法来修复您的代码:

  • 将求和函数作为回调参数传递给setState(并在componentDidMount中调用setState

    this.setState({
        cart: newCart
    }, () => {
        this.sumTotalAmount();
    })
    
  • 使求和函数成为纯函数,并使用新的购物车状态和计算出的总和调用 setState(您基本上是在缓存总和 非透明 在州)

    this.setState({
        cart: newCart,
        sum: calculateSum(newCart),
    })
    
  • 使求和函数成为纯函数,仅在您需要值时使用它(如果您使用了记忆,在这种情况下,您将透明地缓存求和 )

    this.setState({
        cart: newCart,
    })
    
    // in render...
    <div className="cart__total">       
      Total amount - {calculateSum(this.state.cart)}
    </div>