【七日打卡】React中state状态更新那些事

1,076 阅读5分钟

简介

最近听到很多小伙伴面试题上都会碰到一道比较经典的React批量更新(状态合并)的题目,每个人都有各的答法,但其实很少有人能够非常好的给出这一题目的答案,如果你刚好在准备明年跑路,那么这一题看了可能会给你无形中给与一个帮助。

那么,请看题目

  • class 组件中,以下代码同时执行究竟会渲染几次视图,请结合场景作答?
// state默认所有值初始化都为0

this.setState({ a: 1 })
this.setState({ b: 1})
this.setState({ c: 1 })

上述题中,主要问的知识点就是React的渲染更新问题,分别是有状态组件和无状态组件在更新state行为的一个作用变化结果的思考。如果你平时善于实践,那么这种题目实际上算是送分题,通过不难,但是回答的满意就很让人头疼了,这个题目能够向下深挖出对React机制的一些理解。

什么是批量更新?

React Class Component中,setState会触发DOM的一个render,其实也变相的是激发Virtual-Dom的一个更新行为,那么批量更新的操作模式就诞生了。

我们在消费一件商品的时候,扫码支付并不会故意存在分多个批次给商家转账的情况,而是一次性转入到账。所以,在一笔消费的方法中,所有setState其实本身只需要进行付账一次,就好比去超市购物,购物车里面有非常的东西,但是我们最终都是统一结账掉的,并不是买一件,付一件。

因此,setState,看起来是下面这个样子的,在同一批次的setState的值,都会被添加到合并队列中去,进行一个state合并的过程。

setState<K extends keyof S>(
  state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
  callback?: () => void
): void;

forceUpdate(callback?: () => void): void;

举个例子:如下,就是一个未合并之前的一个函数行为,分别分三次设置了不同的state的值。如果不加以处理,那么将会渲染三次render函数的触发。

合并前 =>

this.setState({a: 1})
this.setState({b: 1})
this.setState({c: 1})

可以看到三次行为都被拉入一批队列中,进行了值合并。

合并后 =>

this.setState({
	a: 1,
    b: 1,
    c: 1,
})

异步触发更新时

虽然React为我们做了更新的合并,但是这往往都是取决于React能够鉴定你触发更新行为的时候。但是,当我们的setState在异步中的时候,React并不知道我们还有更新需要执行,因此,他以为你这个函数不需要合并更新,那么它就不会给你进行一个state 合并的操作了。可以看下面这张运行图:

更新

在执行Promise结果中处理的setState并没有合并,依旧被执行了三次,意味着render视图被执行了三次更新,这个无疑是非常浪费,对性能来说产生了无意义的开销。

那么到了这里,大家获取就明白了很多了,如果我们使用了React本身无法监听调度的API的话,setState的事件合并就需要开发者来做调度了。

调度更新

React中,暴露出两个可以自主触发视图更新的API能够给开发者手动更新。

forceUpdate

forceUpdate就非常好理解了,调用这个函数的时候会直接强制刷新。传入的函数则是刷新后的一个回调事件。从下面类型推导来看的话,它的作用并不理想(不推荐使用),对正常开发情况下来说,如果需要涉及强制更新的话,往往是代码存在一些问题,大多数情况下都不需要使用到。

forceUpdate(callback?: () => void): void;

unstable_batchedUpdates

unstable_batchedUpdates则是手动合并处理事件了,之前的更新调度是React帮我们做的,而现在的话相当于我们自己去声明自己的调度,来主动合并事件。

但是,细心的小伙伴可能发现了, unstable_batchedUpdates带了unstable来表明是不稳定的,虽然说使用上来讲是没有问题,但还是不推荐进行使用。

handelClick = () => {
    mockData().then(() => {
      margeUpdate(() => {
        this.setState({a: 1})
        this.setState({b: 1})
        this.setState({c: 1})
      })
    })
  }

开发时合并

更加理想的情况下是主动在开发时,能够合并的State尽量合并成为一个对象,而不是拆成零散的属性,这样对React的一些分担压力还是蛮大的。

// 未合并

state = {
  a: 1,
  b: 2,
  c: 3
}

// 合并

state = {
	obj: {
    a: 1,
    b: 2,
    c: 3
  }
}

看看Hooks

其实函数组件也有相似的问题,而且更加的直观,因为函数组件本身没有状态,所有的状态行为都来自于副作用,当副作用频繁触发的时候,useEffect相关满足依赖的行为可能会被频繁的触发。

总结

不论是在函数组件还是Class组件,在开发的时候都需要合理根据业务定制好state的数据结构,这样的话在后续做数据更新时,才可能尽量少的情况下做视图更新,节省性能。

setState本身没有异步的同步的区分,如同useState的副作用一样正常情况下都是同步执行的,但是它们的上下文(Context)可能存在非同步的条件下会被跟着走。这点需要注意。setState的事件模式都来自于其调度者。

承上启下,那么开头的问题,你知道怎么回事了吗。

7日更新还是太难了,文章质量下滑的较为严重,不过对于基础性的东西还是可以一看的,当作快餐文来消化是一个比较好的选择。后续活动结束后会认真思考一些技术点总结带给大家的。QAQ,猛男落泪。