首页 > 解决方案 > 调度在 UseEffect 内与 Async 函数一起触发

问题描述

有一个 Profile 组件可以使用异步函数创建 3D 面部。为了通知用户等待,在UseEffect中的繁重任务之前和之后触发了两次调度。问题是这两个派发的只有在繁重的任务完成后才开火。

Profile.js

const Profile = (props) => {
  const [face3D, setFace3D] = useState(null)
  const { state, dispatch } = useContext(UserContext)

  const drawFace = async () => {
    // a heavy calculation
  }

  useEffect(() => {
    // show user loading
    dispatch({
      type: "USER_LOADING",
      payload: true
    })

    drawFace().then( face => {
      setFace3D(face)

      // clear user loading
      dispatch({
        type: "USER_LOADING",
        payload: false
      })
    })

  }, [props.data])

  return (
    <Canvas>
      { face3D }
    </Canvas>
  )
}

 export default Profile

UserContext.js

import React, { createContext, useReducer } from 'react'
import { createReducer } from 'react-use'
import logger from 'redux-logger'
import thunk from 'redux-thunk'

export const UserContext = createContext()
const useThunkReducer = createReducer(thunk, logger);

const UserContextProvider = (props) => {
const initialState = {
  loading: false
}

const [state, dispatch] = useThunkReducer(UserReducer, initialState)

return (
  <UserContext.Provider value={{ state, dispatch }}>
    { props.children }
  </UserContext.Provider>
)
}

export default UserContextProvider

PS:redux-logger显示了正确的dispatch顺序,即使记录reducer显示将状态更改为true并最终更改为false但状态仅反映最后一次调度(类似于突变状态的情况,但未突变)

标签: reactjsredux-thunkuse-contextuse-reducer

解决方案


因为您已经注意到这drawFace是一个“繁重的计算”,所以我怀疑它实际上并不是异步的。我认为正在发生的是:

  1. 第一个dispatch叫做
  2. 执行 drawFace 并计算其返回值。因为函数是async,所以结果被包装在一个 Promise 中。
  3. .then()向事件循环添加一个任务,该任务调用setFace3D和第二次调用dispatch.

超时后尝试调用drawFace:

dispatch(...)

setTimeout(() => {
  drawFace().then(face => {
    setFace3D(face);
    dispatch(...)
  })
}, 0)

标记函数async不会自动在后台线程或类似的东西上运行函数体。相反,它做了两件事:

  • 允许使用await关键字
  • 自动将返回的值(或抛出的错误)包装在 Promise 中。

请注意,在以下示例中,“calculate”记录在“after call calculate”之前,即使该函数标记为async

async function calculate() {
  console.log("calculate")
}

console.log("before calling calculate")
calculate().then(() => { 
  console.log("calculation done")
})
console.log("after calling calculate")

// Result:
// "before calling calculate"
// "calculate"
// "after calling calculate"
// "calculation done"

推荐阅读