调用 this.setState 后重置函数变量值

Function variable value resetting after calling this.setState

我是 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

}

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

调用setStatenewPrice等于0的原因是React状态更新是异步。状态更新后的代码会运行beforesetState实际做一些事情,所以在调用this.updatePurchaseable(copyState, newPrice);阶段所有的计算newPrice 尚未执行。

BTW - 这也是为什么您的 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);
}

React 状态更新是 异步setState 功能是完全 同步,所以当你调用updatePurchaseablenewPrice还没有更新。将所有额外的“状态更新后”逻辑移动到 componentDidUpdate 生命周期方法中,这样您就可以 access/reference 更新后的 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
    }
  });
}

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.
  2. 上的文档
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);
}