关于javascript:深入浅出-setState-原理篇

2次阅读

共计 4298 个字符,预计需要花费 11 分钟才能阅读完成。

前言

想起本人(2021 年)8 月份面试时,被面试官们问了好几个 setState 的问题,当初想想,尽管答复上问题,然而理解得不粗浅。我晓得 setState 被设计成“异步”是为了性能,然而波及到源码解读我就歇菜了;我晓得如何让它同步,然而遇到实在的代码状况时,却不晓得如何下手。说到底,过后是筹备了面经把这些概念记下来,而没有真正了解它

在意识 setState 前,咱们问几个常见问题

  • setState 是同步还是异步?
  • 如果是异步,怎么让它同步?
  • 为什么要这样设计?

基本概念和应用

React 的理念之一是 UI=f(data),批改 data 即驱动 UI 变动,那么怎么批改呢?React 提供了一个 API ——setState(类组件的批改办法)

官网介绍:

setState() 将对组件 state 的更新排入队列,并告诉 React 须要应用更新后的 state 从新渲染此组件及其子组件。这是用于更新用户界面以响应事件处理器和解决服务器数据的次要形式

为了更好的感知性能,React 会提早调用它,而后通过一次传递更新多个组件。React 并不会保障 state 的变更会立刻失效

setState() 并不总是立刻更新组件。它会批量推延更新。这使得在调用 setState() 后立刻读取 this.state 成为了隐患。为了消除隐患,请应用 componentDidUpdate 或者 setState 的回调函数(setState(updater, callback)),这两种形式都能够保障在利用更新后触发

除非 shouldComponentUpdate() 返回 false,否则 setState() 将始终执行从新渲染操作。如果可变对象被应用,且无奈在 shouldComponentUpdate() 中实现条件渲染,那么仅在新旧状态不统一调用 setState() 能够防止不必要的从新渲染

应用办法

setState(updater, [callback])

参数一为带有形式参数的 updater 函数:

(state, props) => stateChange

// 例如
// this.setState((state, props) => {//   return {counter: state.counter + props.step};
// });

setState 的第一个参数除了承受函数外,还能够承受对象类型:

setState(stateChange[, callback])

// 例如:this.setState({count: 2})

setState 的第二个参数为可选的回调函数,它将在 setState 实现合并从新渲染组件后执行。通常,咱们倡议应用 componentDidUpdate 来代替此办法

setState(stateChange[, callback])

// 例如: this.setState({count: 2}, () => {console.log(this.state.count)}) 

与 setState 回调相比,应用 componentDidUpdate 有什么劣势?

stackoverflow 有人问过,也有人答复过:

  • 统一的逻辑
  • 批量更新
  • 什么时候 setState 会比拟好?

    • 当内部代码须要期待状态更新时,如 Promise

setState 的个性——批处理

如果在同一周期内对多个 setState 进行解决,例如,在同一周期内屡次设置商品数据,相当于:

this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
// === 
Object.assign(
  count,
  {quantity: state.quantity + 1},
  {quantity: state.quantity + 1},
  ...
)

后调的 setState 将笼罩同一周期内先调用 setState 的值

  • setState(stateChange[, callback])
  • setState((state, props) => stateChange[, callback])

setState 必引发更新过程,但不肯定会引发 render 被执行,因为 shouldCompomentUpdate 能够返回 false

批处理引发的问题

问题 1:间断应用 setState,为什么不能实时扭转

state.count = 0;
this.setState({count: state.count + 1}); 
this.setState({count: state.count + 1}); 
this.setState({count: state.count + 1}); 
// state.count === 1,不是 3

因为 this.setState 办法为会进行批处理,后调的 setState 会笼罩对立周期内先调用的 setState 的值,如下图所示:

state.count = 0;
this.setState({count: state.count + 2}); 
this.setState({count: state.count + 3}); 
this.setState({count: state.count + 4}); 
// state.count === 4

问题 2:为什么要 setState,而不是间接 this.state.xx = oo?

因为 setState 做的事件不仅仅只是批改了 this.state 的值,另外最重要的是它会触发 React 的更新机制,会进行 diff,而后将 patch 局部更新到实在 dom 里

如果你间接 this.state.xx = oo 的话,state 的值的确会改,然而它不会驱动 React 重渲染。setState 能帮忙咱们更新视图,引发 shouldComponentUpdate、render 等一系列函数的调用。至于批处理,React 会将 setState 的成果放入队列中,在事件完结之后产生一次从新渲染,为的就是把 Virtual DOM 和 DOM 树操作降到最小,用于进步性能

当调用 setState 后,React 的 生命周期函数 会顺次程序执行

  • static getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate

问题 3:那为什么会呈现异步的状况呢?(为什么这么设计?)

因为性能优化。如果每次 setState 都要更新数据,更新过程就要走五个生命周期,走完一轮生命周期再拿 render 函数的后果去做 diff 比照和更新实在 DOM,会很耗时间。所以将每次调用都放一起做一次性解决,能升高对 DOM 的操作,进步利用性能

问题 4:那如何在体现出异步的函数里能够精确拿到更新后的 state 呢?

通过第二个参数 setState(partialState, callback) 中的 callback 拿到更新后的后果

onHandleClick() {
    this.setState(
        {count: this.state.count + 1,},
        () => {console.log("点击之后的回调", this.state.count); // 最新值
        }
    );
}

或者能够间接给 state 传递函数来体现出同步的状况

this.setState(state => {console.log("函数模式", state.count);
    return {count: state.count + 1};
});

执行原理

首先先理解三种渲染模式

  • legacy 模式:ReactDOM.render(<App />, rootNode)。这是以后 React app 应用的形式。以后没有打算删除本模式,然而这个模式可能不反对新性能
  • blocking 模式:ReactDOM.createBlockingRoot(rootNode).render(<App />)。目前正在试验中,作为迁徙到 concurrent 模式的第一个步骤
  • concurrent 模式:ReactDOM.createRoot(rootNode).render(<App />)。目前在试验中,将来稳固之后,打算作为 React 的模式开发模式。这个模式开启了所有的新性能

    • 领有不同的优先级,更新的过程能够被打断

在 legacy 模式下,在 React 的 setState 函数实现中,会依据一个变量 isBatchingUpdates 判断是间接更新 this.state 还是放到队列中回头再说,而 isBatchingUpdates 默认是 false,也就示意 setState 会同步更新 this.state,然而,有一个函数 batchedUpdates,这个函数会把 isBatchingUpdates 批改为 true,而当 React 在调用事件处理函数之前就会调用这个 batchedUpdates,造成的结果,就是由 React 管制的事件处理过程 setState 不会同步更新 this.state

像 addEventListener 绑定的原生事件、setTimeout/setInterval 会走同步,除此之外,也就是 React 管制的事件处理 setState 会异步

而 concurrent 模式都是异步,这也是将来 React 18 的默认模式

总结

首先,咱们总结下要害知识点

  • setState 不会立刻扭转 React 组件中 state 的值
  • setState 通过引发一次组件的更新过程来引发从新绘制
  • 屡次 setState 函数调用产生的成果会合并(批处理)

其次,答复一下文章结尾的问题(第二第三问题在文中曾经答复)

setState 是同步还是异步?

  • 代码同步,渲染看模式

    • legacy 模式,非原生事件、setTimeout/setInterval 的状况下为异步;addEventListener 绑定原生事件、setTimeout/setInterval 时会同步
    • concurrent 模式:异步

参考资料

  • setState:这个 API 设计到底怎么样
  • setState 为什么不会同步更新组件状态
  • setState 何时同步更新状态
  • 浅入深出 setState(上篇)
  • 浅入深出 setState(下篇)
  • 重新认识 React 的 setState
  • 你真的了解 setState 吗?
  • setState 到底是同步的,还是异步的
  • React 中 setState 是一个宏工作还是微工作?
  • What is the advantage of using componentDidUpdate over the setState callback?
  • 深刻学习:何时以及为什么 setState() 会批量执行?
  • 深刻:为什么不间接更新 this.state?
正文完
 0