React useState 获取最新值

53 min read
function App() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(2);
    func(); // 点击后打印 0
  };

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

  return (
    <div className="App">
      <button onClick={handleClick}>点击</button>
    </div>
  );
}

控制台打印的还是上一次的值0,而不是最新的2

原因

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

宏任务

来自 微任务、宏任务与Event-Loop

这个就像去银行办业务一样,先要取号进行排号。
一般上边都会印着类似:“您的号码为XX,前边还有XX人。”之类的字样。

因为柜员同时职能处理一个来办理业务的客户,这时每一个来办理业务的人就可以认为是银行柜员的一个宏任务来存在的,当柜员处理完当前客户的问题以后,选择接待下一位,广播报号,也就是下一个宏任务的开始。
所以多个宏任务合在一起就可以认为说有一个任务队列在这,里边是当前银行中所有排号的客户。
任务队列中的都是已经完成的异步操作,而不是说注册一个异步任务就会被放在这个任务队列中,就像在银行中排号,如果叫到你的时候你不在,那么你当前的号牌就作废了,柜员会选择直接跳过进行下一个客户的业务处理,等你回来以后还需要重新取号

而且一个宏任务在执行的过程中,是可以添加一些微任务的,就像在柜台办理业务,你前边的一位老大爷可能在存款,在存款这个业务办理完以后,柜员会问老大爷还有没有其他需要办理的业务,这时老大爷想了一下:“最近P2P爆雷有点儿多,是不是要选择稳一些的理财呢”,然后告诉柜员说,要办一些理财的业务,这时候柜员肯定不能告诉老大爷说:“您再上后边取个号去,重新排队”。
所以本来快轮到你来办理业务,会因为老大爷临时添加的“理财业务”而往后推。
也许老大爷在办完理财以后还想 再办一个信用卡?或者 再买点儿纪念币
无论是什么需求,只要是柜员能够帮她办理的,都会在处理你的业务之前来做这些事情,这些都可以认为是微任务。

console.log('script start');

setTimeout(function() {
  console.log('timeout1');
}, 10);

new Promise(resolve => {
    console.log('promise1');
    resolve();
    setTimeout(() => console.log('timeout2'), 10);
}).then(function() {
    console.log('then1')
})

console.log('script end');

上面的盒子中, 遇到 Promise 实例。Promise 构造函数中的第一个参数,是在 new 的时候执行,构造函数执行时,里面的参数进入执行栈执行;而后续的 .then 则会被分发到 microtask 的 Promise 队列中去。所以会先输出 promise1,然后执行 resolve,将 then1 分配到对应队列。

结论: 每次执行完一个宏任务之后,会去检查是否存在 Microtasks;如果有,则执行 Microtasks 直至清空 Microtask Queue。

setTimeout(_ => console.log(4))

new Promise(resolve => {
  resolve()
  console.log(1)
}).then(_ => {
  console.log(3)
})

console.log(2)

实现useSyncCallback

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;

使用

function App() {
  const [count, setCount] = useState(0);

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

  return (
    <div className="App">
       <button onClick={handleClick}>点击</button>
    </div>
  );
}

export default App;