setState是同步还是异步

setState是同步还是异步

在react中,修改状态如果直接使用this.state,不会引起组件的重新渲染,需要通过 this.setState来对组件的属性进行修改。

1、this.setState的两种定义方式

定义初始状态

state = { count: 0 },

如果此时有一个按钮,点击按钮让计数加1,我们可以有两种写法

(1)传递对象 
this.setState({ count: this.state.count + 1})

(2)传递函数 
this.setState((state, props) => ({ count: count + 1}))

2、setState的两种方式有什么不同?

如果变更的state的值需要依赖于上一次的state的值,这种情况就需要用到函数的形式,比如以下这种情况

addCount(){
    this.setState({ count: this.state.count + 1})
    this.setState({ count: this.state.count + 1})
    this.setState({ count: this.state.count + 1})
}

此时只会执行一次+1的操作,因为在React内部,会将多次setState合并操作,新的state由 Object.assgin({}, {count: 0}, { count: 1}) 合并所得,以上赋值会执行三次,但因为count的值没有更新,所以最终执行的结果只+1,如果setState的赋值是函数,那情况就不一样了

addCount(){
    this.setState((state, props) => ({ count: count + 1}))
    this.setState((state, props) => ({ count: count + 1}))
    this.setState((state, props) => ({ count: count + 1}))
}

这样的操作会得到+3的效果,因为React会进行判断,如果传入的是函数,那么将执行此函数,此时count的值就已经被修改了

3、setState是同步还是异步的?

☆☆☆☆☆ 是异步的

(1) 即我们通过this.setState修改了状态之后,在它的下一行输出state的值并不会得到新的值

(2) 为什么是异步?
有两个原因,一是提高效率,每次修改state的值都会造成render的重新渲染,将多次修改state的值合并统一更新可以提高性能;二是render的更新会晚一些,如果render中有子组件,子组件的props依赖于父组件的state,props和state就不能保持一致

(3) 如何获取异步时的state值?
① 在setState的回调函数中

this.setState({ 
    count: this.state.count + 1}}, 
    ()=>{ console.log(this.state.count)})

② 在componentDidUpdate中获取

componentDidUpdate(){
    console.log(this.state.count)
}

☆☆☆☆☆ 是同步的

(1) 即我们通过this.setState修改状态之后,在它的下一行输出state是新的值

(2) 什么场景下是同步的?
① 原生js获取dom元素,并绑定事件

<button id="addBtn">点我+1</button>
componentDidMount(){
     const addBtn = document.getElementById('addBtn')
     changeBtn.addEventListener('click',()=>{
             this.setState({ count:  this.state.count + 1})
             console.log(this.state.message)
     })
}

② 计时器 setTimeout

<button onClick={ e => this.addOne() }>点我+1</button>
addOne(){
setTimeout(()=>{ this.setState({ count:  this.state.count + 1 })
  console.log(this.state.count ) },0)
}

如何实现useState改变值之后立刻获取最新的状态?

之前铺垫了一堆,终于到正题了,本想直奔主题,一不小心变成了介绍react-hooks的文章,不过多了解一点终究不会亏什么,react-hooks的思想其实是同步逻辑,但是在react的合成事件中状态更新是异步的,看一下下面这个场景,使用useState声明了一个状态state,又分别声明了两个函数setT和func,在setT中,调用setstate更新状态,然后调用func函数,在func中直接打印当前状态state

function App() {
  const [state, setstate] = useState(0);

  const setT = () => {
    setstate(2);
    func();
  };

  const func = () => {
    console.log(state);
  };

  return (
    <div className="App">
      <header className="App-header">
        <button onClick={setT}>set 2</button>
      </header>
    </div>
  );
}

当我们点击set 2 按钮时,控制台打印的还是上一次的值0,而不是最新的2

因为在react合成事件中改变状态是异步的,出于减少render次数,react会收集所有状态变更,然后比对优化,最后做一次变更,在代码中可以看出,func的调用和setstate在同一个宏任务中,这是react还没有render,所以直接使用state获取的肯定是上一次闭包里的值0

有的人可能会说,直接将最新的值当作参数传递给func不就行了吗,对,这也是一种解决办法,但是有时不只是一个状态,可能要传递的参数很多,再有也是出于对react-hooks的深入研究,所以我选择通过自定义hooks实现在useState改变值之后立刻获取到最新的值,我们先看下实现的效果,代码变更如下:

function App() {
  const [state, setstate] = useState(0);

  const setT = () => {
    setstate(2);
    func();
  };
  /** 将func的方法传递给useSyncCallback然后返回一个新的函数 */
  const func = useSyncCallback(() => {
    console.log(state);
  });

  return (
    <div className="App">
      <header className="App-header">
        <button onClick={setT}>set 2</button>
      </header>
    </div>
  );
}

export default App;

效果如下:

可以看出当我们点击set 2按钮之后,控制台输出的直接是2,所以达到了在setstate之后立刻获取了最新状态

三、如何实现useSyncCallback?
先把代码放到这里,大家可以看一下

/*
 * @lastTime: 2021-03-05 15:29:11
 * @Description: 同步hooks
 */

import { useEffect, useState, useCallback } from 'react'

const useSyncCallback = callback => {
    const [proxyState, setProxyState] = useState({ current: false })

    const Func = useCallback(() => {
        setProxyState({ current: true })
    }, [proxyState])

    useEffect(() => {
        if (proxyState.current === true) setProxyState({ current: false })
    }, [proxyState])

    useEffect(() => {
        proxyState.current && callback()
    })

    return Func
}

export default useSyncCallback
/*
 * @lastTime: 2021-02-26 15:29:11
 * @param: callback为回调函数
 * @Description: 用法 const newFunc = useSyncCallback(yourCallback)
 */

在这里还需要介绍一个知识点useEffect,useEffect会在每次函数式组件render之后执行,可以通过传递第二个参数,配置依赖项,当依赖项变更useEffect才会更新,如果第二个参数传递一个空数组[],那么useEffect就会只执行一次,react-hooks正是使用useEffect来模拟componentDidMount和componentDidUpdate两个生命周期,也可以通过在useEffect中return一个函数模拟componentWillUnmount,那么useEffect是如何实现在render之后调用的呢,原理就是在useEffect hooks中会封装一个宏任务,然后把传进来的回调函数放到宏任务中去执行,下面实现了一个简版的useEffect:

/** @param hookStates 存储所有hooks */
const hookStates = [];

/** @param hookIndex 索引 */
let hookIndex = 0;

/**
 *
 * useReducer
 * @param {*} callback 副作用函数
 * @param {*} dependencies 依赖项
 * @returns
 */
function useEffect(callback, dependencies) {
  /** 判断当前索引下hookStates是否存在值 */
  if (hookStates[hookIndex]) {
    /** 如果存在就取出,并结构出destroyFunc销毁函数,和上一次依赖项 */
    const [destroyFunc, lastDep] = hookStates[hookIndex];
    /** 判断当前依赖项中的值和上次依赖项中的值有没有变化 */
    const isSame =
      dependencies &&
      dependencies.every((dep, index) => dep === lastDep[index]);
    if (isSame) {
      /** 如果没有变化就把索引加一,hooks向后遍历 */
      hookIndex++;
    } else {
      /** 如果有变化,并且存在销毁函数,就先调用销毁函数 */
      destroyFunc && destroyFunc();
      /** 创建一个新的宏任务,调用callback获取最新的销毁函数,并将销毁函数和依赖项存入hookStates中 */
      setTimeout(() => {
        const destroyFunction = callback();
        hookStates[hookIndex++] = [destroyFunction, dependencies];
      });
    }
  } else {
    /** 如果hookStates中不存在,证明是首次执行,直接创建一个宏任务,调用callback获取最新的销毁函数,并将销毁函数和依赖项存入hookStates中 */
    setTimeout(() => {
      const destroyFunction = callback();
      hookStates[hookIndex++] = [destroyFunction, dependencies];
    });
  }
}

我已经将useEffect的实现步骤写到了注释里,有兴趣的可以看一下,接下来我们就来使用useEffect实现我们想要的结果,因为useEffect是在react组件render之后才会执行,所以在useEffect获取的状态一定是最新的,所以利用这一点,把我们写的函数放到useEffect执行,函数里获取的状态就一定是最新的

  useEffect(() => {
        proxyState.current && callback()
    })

首先,在useSyncCallback中创建一个标示proxyState,初始的时候会把proxyState的current值赋成false,在callback执行之前会先判断current是否为true,如果为true就允许callback执行,若果为false,就跳过不执行,因为useEffect在组件render之后,只要依赖项有变化就会执行,所以我们无法掌控我们的函数执行,在useSyncCallback中创建一个新的函数Func,并返回,通过这个Func来模拟函数调用,

const [proxyState, setProxyState] = useState({ current: false })

在这个函数中我们要做的就是变更prxoyState的current值为true,来使得让callback被调用的条件成立,同时触发react组件render这样内部的useEffect就会执行,随后调用callback实现我们想要的效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值