【问题标题】:Function variable value resetting after calling this.setState调用 this.setState 后函数变量值重置
【发布时间】:2021-09-22 01:50:33
【问题描述】:

我对 JavaScript 世界比较陌生,我正在学习 react 并遇到了一个奇怪的问题 查看此代码

addIngredientHandler = (type) => {

    let oldCount  = this.state.ingredients[type];
    let copyState = {...this.state.ingredients};

    let newPrice = 0;

    copyState[type] = oldCount + 1;

    this.setState( (prevState, prevProps) => {

        newPrice = prevState.totalPrice + PRICES_OF_INGREDIENTS[type];

        newPrice =  Math.round(newPrice * 100) / 100;

        console.log('newprice inside setState: ' + newPrice);
        
        return { ingredients: copyState, totalPrice:  newPrice}
        

    } );

    console.log('newprice outside setState: ' + newPrice);

    this.updatePurchaseable(copyState, newPrice);


}

这里我关心的是 newPrice 变量,它用于在添加更多项目时更新状态,效果很好

问题是在 this.setState return 之后 newPrice 再次被重新测试为 0 所以我不能将它用于底部的功能。

是的,我可以直接使用状态变量,但由于setState 执行的异步性质,我想改为传递变量值。

在控制台中,由于setState 的异步性质,您可以看到首先执行外部控制台日志,然后执行内部日志

也许我没有得到一些产生这种行为的生命周期反应。

这里是状​​态值,值应该不重要,但仍然是为了更好的图片

state = {
    ingredients: {
        salad: 0,
        bacon: 0,
        meat: 0,
        cheese: 0,
    },
    purchasable: false,

    totalPrice: 0

}

任何提示都有帮助,感谢阅读。

【问题讨论】:

    标签: javascript reactjs ecmascript-6 react-state react-lifecycle


    【解决方案1】:

    在调用setState 之后newPrice 等于0 的原因是因为React 状态更新是异步。状态更新后的代码会在setState实际做某事之前运行之前,所以在调用this.updatePurchaseable(copyState, newPrice);的阶段所有newPrice的计算都还没有被执行。

    顺便说一句 - 这也是为什么您的 console.log 以“反向”顺序打印的原因,每次渲染外部日志都会在内​​部日志之前打印。

    对于这个特定的代码示例,我建议您尝试将现在在 setState 回调中的所有计算移到它之外,甚至移到不同的函数中。

    试试这个 -

    calculateNewPrice = (totalPrice, type) => {
        newPrice = totalPrice + PRICES_OF_INGREDIENTS[type];
        newPrice =  Math.round(newPrice * 100) / 100;
    }
    
    addIngredientHandler = (type) => {
        const { totalPrice } = this.state;
        
        let oldCount  = this.state.ingredients[type];
        let copyState = {...this.state.ingredients};
        copyState[type] = oldCount + 1;
    
        const newPrice = calculateNewPrice(totalPrice, type);
    
        this.setState({ ingredients: copyState, totalPrice:  newPrice });
        
        this.updatePurchaseable(copyState, newPrice);
    }
    

    【讨论】:

    • 感谢您的深入解释,我赞成,唯一的原因是我没有选择这个作为答案的原因,我需要 setState 中的匿名乐趣来获得 prevState 和 @chipit24 提供的确切信息,谢谢非常!。
    【解决方案2】:

    this.setState() 被异步调用,因此您不能依赖 this.state 在调用 this.setState() 后立即引用更新的值。阅读FAQ on component state

    如果你想在状态更新后引用newPrice的更新值,你可以:

    1. 使用componentDidUpdate() 生命周期方法。见https://reactjs.org/docs/react-component.html#componentdidupdate
    addIngredientHandler = (type) => {
      let oldCount = this.state.ingredients[type];
      let copyState = { ...this.state.ingredients };
    
      let newPrice = 0;
    
      copyState[type] = oldCount + 1;
    
      this.setState((prevState) => {
        newPrice = prevState.totalPrice + PRICES_OF_INGREDIENTS[type];
        newPrice = Math.round(newPrice * 100) / 100;
    
        return { ingredients: copyState, totalPrice: newPrice }
      });
    }
    
    componentDidUpdate(prevProps, prevState) {
      if (prevState.totalPrice !== this.state.totalPrice) {
        this.updatePurchaseable(this.state.ingredients, this.state.totalPrice);
      }
    }
    
    1. 使用this.setState() 的第二个参数。请参阅https://reactjs.org/docs/react-component.html#setstate 上的文档。
    addIngredientHandler = (type) => {
      let oldCount = this.state.ingredients[type];
      let copyState = { ...this.state.ingredients };
    
      let newPrice = 0;
    
      copyState[type] = oldCount + 1;
    
      this.setState((prevState) => {
        newPrice = prevState.totalPrice + PRICES_OF_INGREDIENTS[type];
        newPrice = Math.round(newPrice * 100) / 100;
    
        return { ingredients: copyState, totalPrice: newPrice }
      }, () => {
        this.updatePurchaseable(this.state.ingredients, this.state.totalPrice);
      });
    }
    
    1. 使用ReactDOM.flushSync()。见https://github.com/reactwg/react-18/discussions/21
    import { flushSync } from 'react-dom';
    
    addIngredientHandler = (type) => {
      let oldCount = this.state.ingredients[type];
      let copyState = { ...this.state.ingredients };
    
      let newPrice = 0;
    
      copyState[type] = oldCount + 1;
    
      flushSync(() => {
        this.setState((prevState) => {
          newPrice = prevState.totalPrice + PRICES_OF_INGREDIENTS[type];
          newPrice = Math.round(newPrice * 100) / 100;
    
          return { ingredients: copyState, totalPrice: newPrice }
        });
      });
    
      this.updatePurchaseable(copyState, newPrice);
    }
    

    如果我要编写此方法,我建议使用componentDidUpdate 生命周期方法,因为这将确保总价格变化时始终调用updatePurchaseable。如果您只在事件处理程序内部调用updatePurchaseable,那么如果价格在该处理程序之外发生变化,您最终可能会遇到错误。

    addIngredientHandler = (type) => {
      this.setState(prevState => {
        let totalPrice = prevState.totalPrice + PRICES_OF_INGREDIENTS[type];
        totalPrice = Math.round(totalPrice * 100) / 100;
    
        return {
          ingredients: {
            ...prevState.ingredients,
            [type]: prevState.ingredients[type] + 1,
          },
          totalPrice,
        };
      });
    }
    
    componentDidUpdate(prevProps, prevState) {
      const { totalPrice, ingredients } = this.state;
    
      if (prevState.totalPrice === totalPrice) {
        /*
        
        Bail early. This is a personal code style preference. It may 
        make things easier to read as it keeps the main logic on the 
        "main line" (un-nested / unindented)
        
        */
        return;
      }
    
      /*
    
      If `updatePurchaseable` is a class method then you don't need to
      pass state to it as it will already have access to `this.state`.
    
      If `updatePurchaseable` contains complicated business logic,
      consider pulling it out into its own module to make it easier 
      to test.
      
      */
      this.updatePurchaseable(ingredients, totalPrice);
    }
    

    【讨论】:

    • 解决方案 #2 和 #3 真的很有趣,我不知道像 #2 这样的事情是可能的,在 setState 本身中传递一个匿名函数以在完成时执行,#3 也是,太棒了!谢谢。
    • @ShantanuBedajna 小心这种模式copyState[type] = oldCount + 1;,因为它在技术上改变了当前的状态对象。请记住,由于copyState 是当前状态的浅表副本,因此所有更深层次的引用实际上仍然引用回从中复制的对象。请记住坚持功能状态更新以从先前状态正确更新,例如递增计数的情况,并应用不可变更新模式,即浅复制所有正在更新的嵌套状态。
    【解决方案3】:

    React 状态更新是异步,但setState 函数完全是同步,所以newPrice当您致电updatePurchaseable 时尚未更新。将所有额外的“状态更新后”逻辑移动到 componentDidUpdate 生命周期方法中,以便您可以访问/引用更新后的 totalPrice 并使用更新后的状态调用 updatePurchaseable

    componentDidUpdate(prevProps, prevState) {
      if (prevState.totalPrice !== this.state.totalPrice) {
        const { ingredients, totalPrice } = this.state;
        console.log('newprice outside setState: ' + totalPrice);
    
        this.updatePurchaseable(ingredients, totalPrice);
      }
    }
    
    addIngredientHandler = (type) => {
      this.setState((prevState, prevProps) => {
        let newPrice = prevState.totalPrice + PRICES_OF_INGREDIENTS[type];
        newPrice =  Math.round(newPrice * 100) / 100;
        return {
          ingredients: {
            ...prevState.ingredients,
            [type]: prevState.ingredients[type] + 1,
          }, 
          totalPrice:  newPrice
        }
      });
    }
    

    【讨论】:

      猜你喜欢
      • 2020-09-16
      • 2020-05-09
      • 1970-01-01
      • 2015-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-08
      • 1970-01-01
      相关资源
      最近更新 更多