coverPiccoverPic

React Hook:支持 async 的 useEffect

前言

众所周知,useEffect接受的回调函数可以返回一个函数进行清除副作用。

ts
  1. useEffect(() => {
  2. const timer = setInterval(() => {
  3. console.log('miao')
  4. }, 1000)
  5. return () => {
  6. clearInterval(timer)
  7. }
  8. }, [])

当回调函数是具有async修饰符时:

ts
  1. useEffect(async () => {
  2. const data = await getData()
  3. }, [])

useEffect会返回如下报错

cmd
  1. Warning: An effect function must not return anything besides a function, which is used for clean-up

async的函数返回一个Promise,燃鹅useEffect要求返回一个函数。

怎么支持 async

可以在回调函数中手动执行一下:

ts
  1. useEffect(() => {
  2. const func = async () => {
  3. // ...
  4. }
  5. func()
  6. }, [])

也有IIFE(Immediately Invoked Function Expression)的形式:

ts
  1. useEffect(() => {
  2. async () => {
  3. // ...
  4. }()
  5. }, [])

可以在返回的函数中调用then方法处理副作用:

ts
  1. useEffect(() => {
  2. const func = async () => {
  3. // ...
  4. }
  5. const ans = func()
  6. return () => {
  7. ans.then(res => {
  8. // ...
  9. })
  10. }
  11. }, [])

自定义 hook

我们不妨抽取相关逻辑来自定义一个 hook。ahook 中的useAsyncEffect就提供了对async的支持,这里也实现一个类似的钩子。有意思的是,ahook 中的useAsyncEffect提供了对AsyncGenerator(这是 ES9 新增的东西,详见文档)的支持。AsyncGenerator是一个支持asyncawait的生成器,这里也来支持一下。

ts
  1. export type asyncEffect = (arg?: unknown) => PromiseLike<unknown> | AsyncGenerator
  2. export const isAsyncGenerator = (effect: unknown): effect is AsyncGenerator => {
  3. return effect instanceof Object && typeof effect[Symbol.asyncIterator] === 'function'
  4. }
  5. export const asyncEffect = (
  6. effectHook: (effect: React.EffectCallback, deps: React.DependencyList) => void
  7. ) => (
  8. effect: asyncEffect, deps: React.DependencyList
  9. ) => {
  10. const callback = useRef(effect)
  11. // 记录 useEffect 的清除函数有没有被调用,副作用被清除后就不应该执行剩下的 AsyncGenerator 了
  12. const canceled = useRef(false)
  13. useEffect(() => {
  14. callback.current = effect
  15. }, [effect])
  16. const run = useCallback(async () => {
  17. canceled.current = false
  18. if (isAsyncGenerator(callback.current)) {
  19. while (true) {
  20. const ans = await callback.current.next()
  21. if (ans.done || canceled.current) {
  22. return ans.value
  23. }
  24. }
  25. } else {
  26. return await callback.current()
  27. }
  28. }, [])
  29. effectHook(() => {
  30. const cleanEffect = run()
  31. return () => {
  32. canceled.current = true
  33. // 这里支持函数返回一个 fulfilled 状态的 Promise 来清除副作用
  34. cleanEffect.then(clean => {
  35. typeof clean === 'function' && clean()
  36. })
  37. }
  38. }, deps)
  39. }
  40. export const useAsyncEffect = asyncEffect(useEffect)
  41. export const useAsyncUpdateEffect = asyncEffect(useUpdateEffect)
0 条评论未登录用户
Ctrl or + Enter 评论
© 2023-2025 LittleRangiferTarandus, All rights reserved.
🌸 Run