likes
comments
collection
share

React-useState在不同模式下的同步/异步问题

作者站长头像
站长
· 阅读数 49

前言

React有三种不同的模式,concurent模式legacy模式,以及处于concurrent和legacy之间的过渡模式blocking

这里主要讲下state 处于 Concurrent模式与 Legacy模式 差异性

大概对应的React版本号:

模式版本
ConcurrentReact18以及之后的版本
LegacyReact17以及之前的版本

React未来的版本都将采用 Concurrent 模式,其采用渐进增强的方式向后兼容。

以下皆在函数组件举例,state的行为 类组件与函数组件 中表现一致。

legacy 模式

钩子函数与合成事件中,state的表现是:批量的、异步的

import { Button } from "antd"
import React, { useState } from "react"


export const Index = () => {
  const [count, setCount] = useState(0)

  const handleClick = () => {
    setCount(1)
    console.log('111--', count)
    setCount(2)
    console.log('222--', count)
    setCount(3)
    console.log('333--', count)
  }

  console.log('render --', count)

  return (
    <div>
      <Button onClick={handleClick}>test</Button>
    </div>
  )
}

打印的结果:

React-useState在不同模式下的同步/异步问题

批量的:调用了三次setCount,但是最后函数组件只渲染一次(只打印一次 render ---3),也就是将批量的更新合并成一次并进行更新的。

异步的:同时打印了三次 count

并且count值打印三次都是0,这是因为函数组件的更新,本质上是重新执行了一次函数,也就是说通过setCount更新的count值,需要在下次函数执行的时候,可以拿到更新后的值。


在异步操作(setTimeout、setInterval)中,state的表现是:非批量的、同步的

import { Button } from "antd"
import React, { useState } from "react"


export const Index = () => {
  const [count, setCount] = useState(0)

  const handleClick = () => {
    setTimeout(() => {
      setCount(1);
      console.log("count读取的是闭包值 111--", count);
      setCount(2);
      console.log("count读取的是闭包值 222--", count);
      setCount(3);
      console.log("count读取的是闭包值 333--", count);
    });
  }

  console.log('render --', count)

  return (
    <div>
      <Button onClick={handleClick}>test</Button>
    </div>
  )
}

打印结果:

React-useState在不同模式下的同步/异步问题

打印结果可以看到,代码是同步执行的,并且每执行一次setCount,都会更新一次函数组件, 也就是说更新是非批量的


unstable_batchedUpdates

在异步操作中,可以使用 unstable_batchedUpdates 实现手动批量更新。

import { Button } from "antd"
import React, { useState } from "react"
import { unstable_batchedUpdates } from "react-dom"

export const Index = () => {
  const [count, setCount] = useState(0)

  const handleClick = () => {
    setTimeout(() => {
      unstable_batchedUpdates(() => {
        setCount(1);
        console.log("count读取的是闭包值 111--", count);
        setCount(2);
        console.log("count读取的是闭包值 222--", count);
        setCount(3);
        console.log("count读取的是闭包值 333--", count);
      })
    });
  }

  console.log('render --', count)

  return (
    <div>
      <Button onClick={handleClick}>test</Button>
    </div>
  )
}

打印结果:

React-useState在不同模式下的同步/异步问题

可以看到,在引入了 unstable_batchedUpdates 改造异步操作的代码,setCount从非批量、同步 变成了 批量、异步。

场景

unstable_batchedUpdates 有一个经常遇到的使用场景,那就是在发起请求,获取结果后需要更新多次的state

import { Button } from "antd";
import React, { useState } from "react";

export const Index = () => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("");
  const [age, setAge] = useState(18);

  const getUserInfoHttp = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        const obj = {
          name: "jack",
          age: 25,
          pageCount: 21,
          data: []
        };
        resolve(obj);
      }, 1000);
    });
  };

  const handleClick = () => {
    getUserInfoHttp().then((res) => {
      setName(res?.name);
      setAge(res?.age);
      setCount(res?.pageCount);
    });
  };

  console.log("render name--", name);
  console.log("render age--", age);
  console.log("render count--", count);

  return (
    <div>
      <Button onClick={handleClick}>test</Button>
    </div>
  );
};

打印结果:

React-useState在不同模式下的同步/异步问题

可以看到打印了三次,也就是页面刷新太频繁了,可以使用 unstable_batchedUpdates 做下优化


import { Button } from "antd";
import React, { useState } from "react";
import { unstable_batchedUpdates } from "react-dom";

export const Index = () => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("");
  const [age, setAge] = useState(18);

  // 发起请求获取信息
  const getUserInfoHttp = () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        const obj = {
          name: "jack",
          age: 25,
          pageCount: 21,
          data: []
        };
        resolve(obj);
      }, 1000);
    });
  };

  const handleClick = () => {
    getUserInfoHttp().then((res) => {
      // 批量更新优化
      unstable_batchedUpdates(() => {
        setName(res?.name);
        setAge(res?.age);
        setCount(res?.pageCount);
      });
    });
  };

  console.log("render name--", name);
  console.log("render age--", age);
  console.log("render count--", count);

  return (
    <div>
      <Button onClick={handleClick}>test</Button>
    </div>
  );
};

打印结果:

React-useState在不同模式下的同步/异步问题

使用了 unstable_batchedUpdates 优化之后,页面只刷新了一次

concurrent 模式

不管是在异步操作中还是在合成事件中,state都表现出:批量的、异步的

1. 合成事件中

import { Button } from "antd"
import { useState } from "react"


export const Index = () => {
  const [count, setCount] = useState(0)

  const handleClick = () => {
    setCount(1)
    console.log('111--', count)
    setCount(2)
    console.log('222--', count)
    setCount(3)
    console.log('333--', count)
  }

  console.log('render --', count)

  return (
    <div>
      <Button onClick={handleClick}>test</Button>
    </div>
  )
}

打印结果:

React-useState在不同模式下的同步/异步问题

2. 在异步操作中

import { Button } from "antd"
import { useState } from "react"


export const Index = () => {
  const [count, setCount] = useState(0)

  const handleClick = () => {
    setTimeout(() => {
      setCount(1);
      console.log("count读取的是闭包值 111--", count);
      setCount(2);
      console.log("count读取的是闭包值 222--", count);
      setCount(3);
      console.log("count读取的是闭包值 333--", count);
    });
  }

  console.log('render --', count)

  return (
    <div>
      <Button onClick={handleClick}>test</Button>
    </div>
  )
}

打印结果:

React-useState在不同模式下的同步/异步问题

flushSync 设置优先级

flushSync 可以将回调函数中的更新任务,放在一个较高的优先级中

使用flushSync 设置优先级

import { Button } from "antd"
import { useState } from "react"
import { flushSync } from "react-dom"


export const Index = () => {
  const [count, setCount] = useState(0)

  const handleClick = () => {
    setCount(1);

    // 设置优先级
    flushSync(() =>{
      setCount(2);
      console.log("count 111--", count);
    })
    setCount(3);
  }

  console.log('render --', count)

  return (
    <div>
      <Button onClick={handleClick}>test</Button>
    </div>
  )
}

打印结果:

React-useState在不同模式下的同步/异步问题

可以看到,先更新了setCount(2),后更新setCount(3)


结论

state 的同步/异步问题,需要看场景、React当前模式

  • 1.Legacy 模式下

    a.在钩子函数和合成事件中,state表现为批量的、异步的

    b.在异步操作中(setTimeout、Promise),state表现为非批量的、同步的

  • 2.Concurrent 模式下

    state表现都是 批量的、异步的

useState 需要注意的一些问题

浅比较

const [userInfo, setUserInfo] = useState({})

const copyData = { ...userInfo } // 进行浅拷贝 重新分配内存空间
copyData.name = 'jack'
setUserInfo(copyData)

以下是无效的写法:

const [userInfo, setUserInfo] = useState({})

userInfo.name = 'jack'
setUserInfo(userInfo) // 不会更新 因为浅比较的结果是,值userInfo没有发生变化

原因分析:在进行setState更新值的时候,React会进行一次浅比较,也就是当前更新的值和上一次的值,是否有发生变化,如果值没有发生变化,那么不进行更新,否则进行更新。浅比较是进行内存地址的比较,因此直接在原来的值上进行修改,相当于没有变化

获取最新值

给 useState 的 dispatch 传递的参数是一个函数,既可获取修改后的值

const [count, setCount] = useState(0)

setCount(value => value + 1) // count = 0 + 1
setCount(value => value + 1) // count = 1 + 1
setCount(value => value + 1) // count = 2 + 1

参考文献

React 的 setState 是同步还是异步

setState的执行机制

React useState和setState到底是同步还是异步

转载自:https://juejin.cn/post/7244420350753243195
评论
请登录