代码之家  ›  专栏  ›  技术社区  ›  Ann Pavlov

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

  •  1
  • Ann Pavlov  · 技术社区  · 7 年前

    我在写一个没有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>
    4 回复  |  直到 7 年前
        1
  •  2
  •   Bryan Scott    7 年前

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

      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滞后,特别是在最后一次删除时,因为对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;
    }
    
        2
  •  2
  •   gorhawk    7 年前

    这个 setState 方法是异步的,因此 this.sumTotalAmount() 几乎什么也做不了。 至少有三种方法可以修复代码:

    • 将sum函数作为回调参数传递给 设置状态 (和电话 设置状态 componentDidMount )

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

      this.setState({
          cart: newCart,
          sum: calculateSum(newCart),
      })
      
    • 将sum函数设为纯函数,并仅在需要值时使用(如果使用memoization,在本例中您将缓存sum 透明地 )

      this.setState({
          cart: newCart,
      })
      
      // in render...
      <div className="cart__total">       
        Total amount - {calculateSum(this.state.cart)}
      </div>
      
        3
  •  2
  •   Bruno Mazzardo    7 年前

    实际上,问题是在setstate之后调用sum方法,setstate是异步的,所以当sum执行时,状态没有改变,setstate接收到第二个参数,状态更新后执行回调,见下面的文档 https://reactjs.org/docs/react-component.html#setstate

    基于prevstate使sumtotalamount工作可以解决这个问题

     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>
        4
  •  0
  •   aravind_reddy    7 年前

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

    2.最初您不计算和,所以它显示为0。 所以试着调用那个函数 sumTotalAmount 在里面 componentDidMount .