【问题标题】:"Good" way how to update state (from its previous value) in React如何在 React 中更新状态(从之前的值)的“好”方式
【发布时间】:2021-11-03 22:51:35
【问题描述】:

来自反应文档

从文档 (https://reactjs.org/docs/handling-events.html) 我可以看到通过箭头功能处理开关按钮更改,例如:

handleClick() {
  this.setState(prevState => ({
    isToggleOn: !prevState.isToggleOn
  }));
}

这里(对我来说)重要的是,新的状态值是从其先前的状态值导出的。 如果我是对的,则行为与(没有箭头功能)相同:

handleClick() {     
    this.setState(function (prevState){
      return {isToggleOn: !prevState.isToggleOn}
    });
}

甚至完全没有功能:

handleClick() {
  this.setState({isToggleOn: this.state.isToggleOn ? 
    false : true});
}

1) 这些除了代码风格有什么不同吗? 所有方法似乎都对我有用,我找不到任何区别(可读性除外)。

2) 我想,this.setState 只是“setter”,如何将“prevState”传递给那个函数?

3)这样写代码是不是错了(第三种方法)?我是从“老派”变成了5年多没有开发,现在我回到了慢慢开发。第一个例子对我来说很难理解(因为箭头函数和匿名函数),第二个更清楚一点,但最后一个对我来说是最不言自明的。


现实生活中的例子

“真正的学习(生活)”示例我现在正在努力: 在我学习的过程中,我正在尝试编写自己的计算器(从基本功能开始)。由于糟糕的“设计”(我发现使用一些数组、字段、列表而不是仅使用“variableA、variableB”会更好,但绝不会更少......)。 我的应用结构是:

  • 计算器
    • 结果头
      • 输入(用于结果)
      • 输入(用于打印方程式)
    • 计算按钮 (0)
      • 按钮
    • ...
    • 计算按钮 (X)
      • 按钮

CalcButton 包含其值的道具([0...9]、x、/、+、-、C、...)和事件处理程序(numberClick、operatorClick)。当按下数字按钮时,它会将其状态提升到计算器,它将数字添加到 valueA 或 valueB,这取决于“isFirst”的状态。 operatorClick 包含其自身功能的开关,如果按下 x、/、+、-,它将 isFirst 设置为 false,用户可以继续使用第二个数字 (valueB)。现在这个处理程序的代码:

onNumberClick(e) {
    function appendA() {
        return {valueA: this.state.valueA + e};
    }

    function appendB() {
        return {valueB: this.state.valueB + e};
    }

    this.setState(this.state.isFirst ? appendA : appendB);

    /*
    if(this.state.isFirst) { 
        this.setState({valueA: this.state.valueA + e});
    } else {
        this.setState({valueB: this.state.valueB + e});
    }
    */
}

注释代码与第三个示例基本相同,即使是未注释的变体,只是有一些“可读性”改进。过去 2 天,如果有可能使用一些匿名箭头函数,我会被卡住,所以我不需要复制代码(一次用于 valueA,一次用于 valueB)。由于 javascript 及其缺乏对象引用,我能想到的唯一解决方案是通过字符串 ("valueA" || "valueB") 传递参数,但我认为这不是一个好的解决方案。这里重要的是(如顶部的示例),新状态值是从其先前值派生的(这就是为什么我仍在考虑使用箭头匿名函数的原因)。

有人可以告诉我一些如何实现这一目标的例子吗?这里是否需要箭头函数(甚至可以通过箭头函数来做到这一点?)还是这个“代码”很好,我发现没有问题?

【问题讨论】:

  • 向 setter 传递一个函数是保证您不会获得陈旧值的唯一方法。

标签: javascript reactjs arrow-functions readability state-management


【解决方案1】:

React 批量状态更新。如果在一段代码中使用setState 更新状态,然后代码的其他部分同步再次调用setState,则之前的值集可能不会反映在this.state然而。例如:

class App extends React.Component {
  state = {};
  componentDidMount() {
    this.setState({ foo: 'foo' });
    console.log('after setting state, this.state.foo is:', this.state.foo);
    // so, further logic that depends on this.state.foo being 'foo' will fail
  }
  render() {
    return 'My App';
  }
}

ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>

换句话说,你不能可靠地做到

this.setState({ someProp: someVal });
// refer to this.state.someProp here
// because it may well refer to the state before it was updated
// not the new state which contains someVal

如果像在您的特定情况下一样,您没有多个同步状态更新 - 那么没有问题,因为状态几乎会在一次点击后立即更新,并且在处理另一次点击之前。所以,你的

handleClick() {
  this.setState({isToggleOn: this.state.isToggleOn ? 
    false : true});
}

是安全的 - 或者更简洁地说:

handleClick() {
  this.setState({isToggleOn: !this.state.isToggleOn });
}

只要您不立即在 handleClick 之前或之后执行依赖或更改 this.state.isToggleOn 的操作else

你只需要功能版本,例如

handleClick() {     
    this.setState(function (prevState){
      return {isToggleOn: !prevState.isToggleOn}
    });
}

如果状态可能刚刚由上面的代码设置(同步),则在重新渲染之前。有关示例,请参见此答案中的第一个 sn-p - 在这种情况下,如果您想再次调用 this.setState,并使用刚刚设置的 this.state.foo 的值,则您需要使用回调表单.

我想,this.setState 只是“setter”,“prevState”如何传递给那个函数?

React 内部会在调用 setState 时跟踪状态的当前值,并将更新的值传递给回调,尽管事实上组件的 this.state 可能尚未更新。

这样写代码是不是错了(第三种方法)?

完全没有,只要您知道限制。 (如果您的 setState 不依赖于之前通过调用 this.setState 设置的状态值刚刚设置,那么您的第三种方法就可以了。)

仅举一个人为的示例,如果您的应用有一个增加计数器的按钮,以及另一个按计数器的数量增加状态值的按钮,则一种选择是将回调版本与多次调用结合使用setState:

updateSum() {
  for (let i = 0; i < this.state.count; i++) {
    this.incrementByOne();
  }
}
incrementByOne() {
  this.setState(prev => ({ sum: prev.sum + 1 }));
}

上面的incrementByOne,因为它被多次调用,只有在使用回调版本时才有效。如果有的话

this.setState({ sum: this.state.sum + 1 });

它无法正常运行,因为 this.state.sum 不会从之前对 incrementByOne 的同步调用中更新。

【讨论】:

  • 非常感谢@CertainPerformance 的精彩评论!但是呢: if(this.state.isFirst) { this.setState({ valueA: this.state.valueA + e }, () => console.log(this.state.valueA), ); } else { this.setState({ valueB: this.state.valueB + e }, () => console.log(this.state.valueB), );这是,如果我是对的,同样的回调,对吧?
  • 只有一个我不确定我是否理解正确的是带有箭头函数的回调。这是否意味着“prevState”是 setState 的回调?第一次迭代(init),回调是{}(或未定义?),在这种情况下它没有“.isToggleOn”值,它怎么可能在init之后工作?
  • 这有点不同。您可以选择无、任何一个或两者都选: (1) 将回调作为第一个参数传递给setState。提供的第一个参数将是最新状态,可能尚未反映在 this.state 中。例如this.setState(prevState =&gt; { doStuffWithPrevState }。 (2) 然后是 second 参数,一个可选的回调,在状态更新完成后运行,我在答案中没有提到。 this.setState({ prop: 'newVal' }, () =&gt; console.log('state update finished'))
  • 第一次迭代,回调的参数将是当前状态 - 相当于this.state。在进一步的调用中,参数将是最新的状态(由 React 内部跟踪),但尚未反映在 this.state 中。
  • 非常感谢您的解释。你很酷!真的非常感谢你,我真的很感谢有能力以如此出色的方式向我解释一些事情的人。
猜你喜欢
  • 1970-01-01
  • 2020-10-05
  • 2023-01-28
  • 1970-01-01
  • 2017-11-12
  • 1970-01-01
  • 2021-07-09
  • 1970-01-01
相关资源
最近更新 更多