何时使用 setState 回调函数

setState 因为批量更新存在异步的可能,取的 this.state/this.props 不一定是预期的最新值,使用 componentDidUpdate 或者 setState 的回调函数都可以保证在组件重渲染完成后执行,这时 state 是最新的。

以上是我理解,但具体什么时候使用回调函数来更新 state 呢?React 官方文档的一些线索是

如果后续状态取决于当前状态,我们建议使用 updater 函数的形式代替
https://zh-hans.reactjs.org/d...

我应该如何更新那些依赖于当前的 state 的 state 呢?
给 setState 传递一个函数,而不是一个对象,就可以确保每次的调用都是使用最新版的 state
https://zh-hans.reactjs.org/d...

那么是否可以理解为依赖当前 state 的要用回调函数,但在 React 的教程实现 Toc Tac Toe 的代码中却并不是这样的

handleClick(i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      history: history.concat([{
        squares: squares
      }]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext,
    });
  }

代码中对 historyxIsNext 的计算都依赖于当前的 state 但并没有使用回调函数。

所以 setState 回调函数具体要在什么时机使用呢?

阅读 3.4k
1 个回答

并没有什么黑科技,把你上面的代码结合到下面的react batching逻辑中马上就能理解了

type Payload<T> = ((state: T) => Partial<T>) | Partial<T>
type Update<T> = {
  payload: Payload<T>
}

const React = {
  Component: class Component<T = unknown> {
    state: T | undefined
    updateQueue: Update<T>[] = []

    pending: boolean = false

    processUpdateQueue(): Partial<T> {
      let partialState: Partial<T> = this.state!

      for (const update of this.updateQueue) {
        if (typeof update.payload === 'function') {
          partialState = update.payload(partialState as any)
        } else {
          partialState = update.payload
        }
      }
      
      this.updateQueue.length = 0

      return partialState!
    }

    setState(payload: Payload<T>) {
      this.updateQueue.push({
        payload,
      })

      if (!this.pending) {
        this.pending = true
        queueMicrotask(() => {
          const partialState = this.processUpdateQueue()
          this.state = Object.assign({}, this.state, partialState)
          this.render()
          this.pending = false
        })
      }
    }

    render() {}
  },
}

class App extends React.Component<{ num: number }> {
  state = {
    num: 0,
  }

  constructor() {
    super()
    document.addEventListener('click', () => {
      //分别注释下面的其中的一个块看看分别是什么情况
      
      //参数为对象
      {
        this.setState({ num: this.state.num + 1 })
        this.setState({ num: this.state.num + 1 })
        this.setState({ num: this.state.num + 1 })
      }

      //参数为函数
      {
        this.setState((state) => {
          return {
            num: state.num + 1,
          }
        })
        this.setState((state) => {
          return {
            num: state.num + 1,
          }
        })
        this.setState((state) => {
          return {
            num: state.num + 1,
          }
        })
      }
    })
  }

  render() {
    console.log(this.state.num)
  }
}

new App()

如果是第一个参数是对象则state不会立即发生变化所以下面这种做法可能会不符合预期

this.setState({ num: this.state.num + 1 })
this.setState({ num: this.state.num + 1 })
this.setState({ num: this.state.num + 1 })

看上去加了三次一实际上只有最后一次生效了也就相当于只加了一次一(详细逻辑看上面代码)
而后续的setState要想获得之前setState对state做的变更,只需要参数传函数就行

处理updateQueue时的调用栈
image.png
processUpdateQueue源码
getStateFromUpdate源码
处理不同类型的payload源码

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题