关于React useState 获取最新值的解决方案

302 阅读2分钟

前言

由于React 中的Fiber的调度策略是在主线程空闲时进行批量更新,所以导致state用起来像是异步的一样,就是这种情况导致我们在代码里可能拿不到最新的state。

import { FC, useState } from "react"; 
interface TestProps {}
export const Test: FC<TestProps> = (props) => {
  const [page1,setPage1] = useState(1)
  const [param1,setParam1] = useState(2)
  return (
    <div>
      <button
        onClick={() => {
          setPage1(pre => {
            const res = pre + 2
            console.log("最新的page1:",res)
            return pre + 2
          })
          setParam1(pre => {
            
            const res = pre + page1/**page1 不是最新的 */
            console.log("使用的page1:", page1);
            console.log("最新的param1:", res);
            return res
          })
          /**
           * 输出和页面不匹配
           */
          console.log("===1", { page: page1, param: param1 });
        }}
      >
       正常state: {page1},{param1}
      </button>
    </div>
  );
}

解决思路

使用回调函数进行解决,在当state更新的时候使用useEffect可监听到state的变化,若发生变化,调用传入的回调函数将最新的值传递出去(另外useEffect的return还可以获取改变前的值)。理论成立开始实践。

import { useCallback, useEffect, useRef, useState } from "react"

export const useCallbackState = <S = undefined>(
  initialState: S | (() => S)): [S, (state: S | ((prevState: S) => S),callback?:(state:S)=>unknown) => void] => { 
  const [state, setState] = useState(initialState)
  const callbackRef = useRef<(state: S) => unknown>()
  useEffect(() => { 
    // 当state发生改变,若保存的回调函数存在执行回调函数并将最新的值传出
    if(typeof callbackRef.current == "function"){callbackRef.current(state)}
  },[state])
  const changeState = useCallback((state: S | ((prevState: S) => S), callback?: (state: S) => unknown) => {
    //若存在回调函数那么保存回调函数
    if (typeof callback === "function") { callbackRef.current = callback }
    //设置值
    setState(state)
  },[])
  return [state,changeState]
  }

优化

既然出现了回调函数,那么就会出现回调地狱,(比如值A的回调中修改值B,值B的回调中修改值C...),那么就不难想出 Promise A+ 规范,就是为了规范回调,那么我们也可以使用Promise规范回调,Promise是可以使用async/await来解决回调地狱的。理论成立,开撕。

import {useCallback, useEffect, useRef, useState} from "react";

export const useAsyncState = <S = undefined>(
  initialState: S | (() => S),
): [S, (state: S | ((prevState: S) => S)) => Promise<S>, S | undefined] => {
  const [state, setState] = useState<S>(initialState);
  const resolve = useRef<(value: S | PromiseLike<S>) => void>();
  const stateRef = useRef<S>();
  useEffect(() => {
    // 当值发生改变时 执行resolve方法并把最新的值传递出去
    if (resolve.current) {
      resolve.current(state);
    }
    stateRef.current = state;
  }, [state]);
  const changeState = useCallback((state: S | ((prevState: S) => S)) => {
    // 直接返回一个Promise
    return new Promise<S>((res) => {
      //将Promise的resolve方法存起
      resolve.current = res;
      //设置值
      setState(state);
    });
  }, []);
  return [state, changeState, stateRef.current];
};

最终效果

import { FC, useState } from "react";
import { useAsyncState } from "src/hooks/useAsyncState";
interface TestProps {}
/**
*
*/
export const Test: FC<TestProps> = (props) => {
 const [page, setPage] = useAsyncState(1);
 const [param, setParam] = useAsyncState(2);
 const [page1,setPage1] = useState(1)
 const [param1,setParam1] = useState(2)
 return (
   <div>
     <button
       onClick={() => {
         /* setPage((pre) => {
              return pre + 2;
            }).then((page) => {
              setParam(pre => {return pre + page}).then(param => {
                console.log("===",{page,param})
              })
             }); */
         /**改进 */
         const click = async () => { 
           const resPage = await setPage(pre => { return pre + 2 })
           const resParam = await setParam(pre => { return pre + resPage })
           /**与页面同步 */
           console.log("===", { page: resPage, param: resParam })
         }
         click()
       }}
     >
      使用useAsyncHooks: {page},{param}
     </button>
     <button
       onClick={() => {
         setPage1(pre => {
           const res = pre + 2
           console.log("最新的page1:",res)
           return pre + 2
         })
         setParam1(pre => {
           
           const res = pre + page1/**page1 不是最新的 */
           console.log("使用的page1:", page1);
           console.log("最新的param11:", res);
           return res
         })
         /**
          * 输出和页面不匹配
          */
         console.log("===1", { page: page1, param: param1 });
       }}
     >
      正常state: {page1},{param1}
     </button>
   </div>
 );
};

项目地址
github.com/myselfwly/r…