【问题标题】:Does setState or dispatch (useReducer hook) make component re-render before calling next lines?setState 或 dispatch (useReducer hook) 是否在调用下一行之前重新渲染组件?
【发布时间】:2021-04-25 16:04:51
【问题描述】:

我在使用 useState 和 useReducer 钩子时意识到一个问题,即更新状态函数(setState,dispatch)之后的任何代码行都将在下一次重新渲染(更新前的先前状态)中被调用。这意味着更新状态函数会立即重新渲染,而不是等待整个函数执行。

const [aaa, setAAA] = useState<boolean>(false);

const updateMyBiddingList = async (atDate?: string) => {
    try {
      console.log('step 0');
      const result = await getBiddingCartFromService(atDate ? atDate : myBiddingListState.myBiddingList[0].updatedAt);
      if (result.responseCode.toString().startsWith('2')) {
        setAAA(true);
        console.log('step 1');
      }
      console.log('step 2 ', aaa);
    }
    catch (err) {
      if (timeOut.current) clearTimeout(timeOut.current);
      timeOut.current = setTimeout(() => updateMyBiddingList(), TIMEOUT);
    }
  }

console.log('Component is re-rendering... ', aaa);

return ...

以上代码将按以下顺序登录:

  • 步骤 0
  • 组件正在重新渲染... true
  • 第一步
  • 步骤 2 正确

有人为我解释更新状态挂钩的工作流程吗?提前致谢。

【问题讨论】:

    标签: reactjs typescript react-native react-hooks


    【解决方案1】:

    这是由于 javascript 的异步逻辑而发生的。我认为您在渲染阶段的某处调用了 updateMyBiddingList 函数,javascript 会启动它的进程,并且由于它是异步反应,因此不会等待它完成。每当您发出异步请求时,这都会导致您的应用程序冻结。

    【讨论】:

    • 抱歉回复晚了。我对此做了一些实验,我得到了一件事:如果其中有带有更新状态钩子函数的异步函数,则更新函数将在执行下一行代码之前触发重新渲染。这意味着我上面发布的顺序在api调用中是正确的(副作用)→我不知道它是否与纯函数有关。我已经在正常功能中测试了更新功能,例如按钮的onClick事件的onClickHandler,一切都井井有条:步骤0,步骤1,步骤2,触发重新渲染。太奇怪了。我还没有答案。
    • 我得到了一些新的东西,每个函数都有 await 关键字,用于 Promise 函数调用会导致更新状态钩子函数的奇怪顺序(这个函数会在执行下一行之前触发渲染)
    【解决方案2】:

    这是因为 React 不依赖于这样的异步任务,有两种方法可以模拟你想要的结果。一个我称之为标准版本,一个不稳定版本。

    不稳定版本

    第一种方法是将更新包装在 unstable_batchedUpdates 回调中。顾名思义,此 API 将在一次对账过程中批量更新您的更新,从而减少组件渲染。

    所以更新后的代码会是这样的:

    import { unstable_batchedUpdates } from "react-dom";
    
    
    
    const updateMyBiddingList = async (atDate?: string) => {
        try {
          console.log('step 0');
          const result = await getBiddingCartFromService(atDate ? atDate : myBiddingListState.myBiddingList[0].updatedAt);
          
          // Add unstable API where you cause re-render ⭐
          unstable_batchedUpdates(() => {
            if (result.responseCode.toString().startsWith('2')) {
              setAAA(true);
              console.log('step 1');
            }
            console.log('step 2 ', aaa);
          })
        }
        catch (err) {
          if (timeOut.current) clearTimeout(timeOut.current);
          timeOut.current = setTimeout(() => updateMyBiddingList(), TIMEOUT);
        }
      }
    

    这会将日志的顺序更改为此

    Component is re-rendering...  false
    step 0 
    step 1 
    step 2  false
    Component is re-rendering...  true
    

    您可以在以下代码沙箱中看到使用来自 JSON 占位符的假数据运行的代码。

    标准版(react common 模式)

    这不是你可以做你想做的唯一模式,但它对我来说似乎更像是反应式的:)

    • 您正在请求(异步操作)
    • 您想要更改将导致重新渲染的组件状态

    此阶段还会更改下一次渲染的函数定义

    • 您想在失败时重新获取

    我坚持您的实现,不想将 useEffect 部分拆分为可重复使用的小部分,尽管您可能想考虑一下。

    解决方案是将你的逻辑移到它所属的地方,这意味着你将在你的 dom 树中调用该函数调用。

    
    // instead of aaa and setAAA :)
    const [updated, setUpdated] = useState<boolean>(false)
    const [hasError, setHasError] = useState<boolean>(false)
    const timeOut = useRef<number>() 
    
    useEffect(() => {
      const updateMyBiddingList = async (atDate?: string) => {
         try {
          console.log('step 0');
          const result = await getBiddingCartFromService(atDate ? atDate : myBiddingListState.myBiddingList[0].updatedAt);
          setHasError(false)
          if (result.responseCode.toString().startsWith('2')) {
            setUpdated(true);
            console.log('step 1');
          }
          console.log('step 2 ', updated);
        }
        catch (err) {
          setHasError(true)
          if (timeOut.current) clearTimeout(timeOut.current);
          timeOut.current = setTimeout(() => updateMyBiddingList(), TIMEOUT);
        }
      }
      if (!updated || hasError) updateMyBiddingList()
    
    // timeOut.current can also be added here but it is not recommended
    }, [updated, hasError])
    

    【讨论】:

    • 非常感谢。这就是我想了解的。另外,你的第二种方式给我带来了一些我从未想过的新东西。多么棒的答案!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-04
    • 1970-01-01
    • 2020-08-08
    • 2021-04-04
    • 2018-02-28
    • 1970-01-01
    相关资源
    最近更新 更多