为何setState内无参调用的randomQuoteIndex会先于setState执行?
randomQuoteIndex()会在setState更新状态前执行? 这是个非常典型的JavaScript执行顺序问题,咱们一步步拆解来搞清楚:
首先要明确一个核心点:当你在setState的参数对象里调用this.randomQuoteIndex()时,这个函数会在setState开始执行状态更新之前就被调用了。
背后的原因
JavaScript在处理对象字面量(比如你传给setState的{ quotes, randomQuoteIndex: this.randomQuoteIndex(), ... })时,会先计算所有属性的取值,把每个属性的值都确定下来,再把完整的对象传给setState方法。也就是说:
- 先执行
this.randomQuoteIndex()拿到返回值 - 再把这个值和
quotes、isDoneFetching一起组装成对象 - 最后才调用
setState把这个对象传入,开始更新状态
错误代码的问题所在
看你的错误实现:
componentDidMount() { fetch('https://gist.githubusercontent.com/nataliecardot/0ca0878d2f0c4210e2ed87a5f6947ec7/raw/1802a693d02ea086817e46a42413c0df4c077e3b/quotes.json') .then(response => response.json()) .then(quotes => this.setState({ quotes, randomQuoteIndex: this.randomQuoteIndex(), isDoneFetching: true })); } randomQuoteIndex() { return random(0, this.state.quotes.length - 1); }
当JS计算randomQuoteIndex的属性值时,this.setState还没开始更新this.state——你从fetch拿到的quotes只是一个局部变量,还没被存入组件状态。这时候this.state.quotes还是更新前的旧值(大概率是undefined或者空数组),调用this.randomQuoteIndex()自然会因为length属性不存在而报错。
修正写法的逻辑
而你改成传入quotes参数的写法就完全没问题:
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); }
这里你直接把fetch到的最新quotes(局部变量)传给了函数,不需要依赖this.state里还没更新的值——函数直接用这个现成的局部变量计算索引,等setState执行时,只是把预先计算好的索引值存入状态即可。
额外小技巧:setState的函数式写法
如果你的状态更新需要依赖之前的状态,或者要基于新值计算,推荐使用setState的函数式写法,这样能更安全地处理状态依赖:
this.setState((prevState) => { const newIndex = random(0, quotes.length - 1); return { quotes, randomQuoteIndex: newIndex, isDoneFetching: true }; });
这种写法里,函数内部的逻辑是在setState准备更新状态时才执行的,能彻底避免依赖未更新的旧状态。
内容的提问来源于stack exchange,提问作者nCardot

