为什么我需要使用setState回调为依赖于第一项的setState完成状态的第二个状态项设置状态?

时间:2019-07-08 22:27:15

标签: javascript reactjs

在这个componentDidUpdate方法中,在执行setState以将引号设置为从获取中返回的内容后,我必须再次使用回调执行setState来将randomQuoteIndex设置为调用randomQuoteIndex的结果,该结果依赖于{{1} },即:

this.state.quotes.length

为什么下面的代码不起作用?根据对this question的选定答案,我的印象是setState中的第二个项目要等到第一个项目的状态设置好之后才能应用。如果尝试此操作,则会收到错误“ TypeError:无法读取未定义的属性'quote'”。 (我读到setState是异步的,关于何时使用回调,但是我很难理解在这种情况下我读到的内容/如何应用。)

componentDidMount() {
    fetch('https://gist.githubusercontent.com/nataliecardot/0ca0878d2f0c4210e2ed87a5f6947ec7/raw/1802a693d02ea086817e46a42413c0df4c077e3b/quotes.json')
      // Takes a JSON response string and parses it into JS object
      .then(response => response.json())
      // state is set to quotes: quotes due to destructuring
      // Using setState callback since setState is asynchronous and need to make sure quotes is loaded before setting the randomQuoteIndex state since it depends on it
      .then(quotes => this.setState({ quotes }, () => {
        this.setState({
          randomQuoteIndex: this.randomQuoteIndex(),
          isDoneFetching: true
        })
      }))
  }

这是完整的组件代码(工作版本):

  componentDidMount() {
    fetch('https://gist.githubusercontent.com/nataliecardot/0ca0878d2f0c4210e2ed87a5f6947ec7/raw/1802a693d02ea086817e46a42413c0df4c077e3b/quotes.json')
      // Takes a JSON response string and parses it into JS object
      .then(response => response.json())
      // Using setState callback since setState is asynchronous and need to make sure quotes is loaded before setting the randomQuoteIndex state since it depends on it
      .then(quotes => this.setState({
          quotes,
          randomQuoteIndex: this.randomQuoteIndex(),
          isDoneFetching: true
        }));
  }

2 个答案:

答案 0 :(得分:1)

并非setState是异步的,这是在设置状态之前调用randomQuoteIndex的结果。有或没有异步状态更新都会是这种情况。考虑一下componentDidMount的此重构版本:

  componentDidMount() {
    fetch('https://gist.githubusercontent.com/nataliecardot/0ca0878d2f0c4210e2ed87a5f6947ec7/raw/1802a693d02ea086817e46a42413c0df4c077e3b/quotes.json')
      .then(response => response.json())
      .then(quotes => {
        const newState = {
          randomQuoteIndex: this.randomQuoteIndex(),
          isDoneFetching: true,
          quotes
        }
        this.setState(newState)
      })
  }

在功能上与您在问题中发布的版本完全相同。希望这可以突出显示this.randomQuoteIndex()this.setState(newState)之前进行求值,并且由于尚未调用setState,因此randomQuoteIndex没有依赖的状态。调用setState时,必须先对参数进行求值,然后才能将其传递给setState,无论是否同步,在调用randomQuoteIndex时更新都不会停留。

解决此问题的简单方法是使randomQuoteIndex将引号列表作为参数,而不是将其从组件状态中拉出来。重写后,这对方法可能看起来像:

  componentDidMount() {
    fetch('https://gist.githubusercontent.com/nataliecardot/0ca0878d2f0c4210e2ed87a5f6947ec7/raw/1802a693d02ea086817e46a42413c0df4c077e3b/quotes.json')
      .then(response => response.json())
      .then(quotes => this.setState({
          quotes,
          randomQuoteIndex: this.randomQuoteIndex(quotes),
          isDoneFetching: true
        }));
  }

  randomQuoteIndex(quotes) {
    return random(0, quotes.length - 1);
  }

只需要调用setState一次,(可能)可以节省重新渲染的时间。

答案 1 :(得分:1)

我个人并不真的认为React的作者对setState中的回调的意图是使用它来调用下一个setState。 为什么不尝试上面提到的@Icepickle之类的东西:

function randomQuoteIndex(quotes) {
  return random(0, quotes.length - 1);
}
...
...
  .then(quotes => {
    this.setState({
      quotes,
      randomQuoteIndex: randomQuoteIndex(quotes),
      isDoneFetching: true
    })
  }))
...

您只更新一次状态=>确保您始终只有一个渲染周期