useEffect 为什么不能支持 async function?

2021-02-20 15:27:36 +08:00
 wxsm
  useEffect(async () => {
   await loadContent();
  }, []);

这种写法,应该是非常常见的需求。但是 React 本身并不支持这么做,理由是 effect function 应该返回一个销毁函数,如果用上了 async,返回值则变成了 Promise,会导致 react 在调用销毁函数的时候报错 (function.apply is undefined)。

因此,React 推荐的两种写法:

  useEffect(() => {
    // Create an scoped async function in the hook
    async function anyNameFunction() {
      await loadContent();
    }
    // Execute the created function directly
    anyNameFunction();
  }, []);

和:

  useEffect(() => {
    // Using an IIFE
    (async function anyNameFunction() {
      await loadContent();
    })();
  }, []);

我觉得都非常地不优雅,极度增加心智负担。

  1. 如无必要,勿增实体。第一种方式除了定义了一个无意义的函数(还得想办法给它取名)以外,IDE 还会报 warning ( Promise 未处理)。
  2. 至于方式二,更加无力吐槽。ES6 时代都 5 年了,还搞 IIFE,纯粹就是恶心人。

React 判断 useEffect 的传参到底是纯函数还是 Promise 非常简单,它为什么不做呢?这到底是设计缺陷,还是 React 偷懒,还是我傻逼?

6177 次点击
所在节点    程序员
74 条回复
weixiangzhe
2021-02-20 15:41:53 +08:00
这样写也挺危险的, 建议
weixiangzhe
2021-02-20 15:41:59 +08:00
weixiangzhe
2021-02-20 15:44:29 +08:00
主要是 用 async,也相当于这样吧

```
useEffect(()=> new Promise(()=>{
/**xxxx**/
}))
```
也就是 unmount 时的清理函数了
ayase252
2021-02-20 15:46:50 +08:00
我猜 useEffect 的返回值是要在卸载组件时调用的,React 需要在 mount 的时候马上拿到这个值,不然就乱套了
islxyqwe
2021-02-20 15:47:30 +08:00
useEffect(() => {
loadContent().then(()=>{/** do something */});
}, []);
love
2021-02-20 15:53:20 +08:00
??你直接写个封装 hook 不就看上去优雅了,比如
useAsyncEffect(async ()=>{})
wxsm
2021-02-20 15:53:37 +08:00
@islxyqwe 丑陋的写法
wxsm
2021-02-20 15:54:57 +08:00
@weixiangzhe 我觉得不够优雅
wxsm
2021-02-20 15:59:24 +08:00
@love 我可以封装,我只是想知道 react 为什么不直接给用户提供,而要留下坑人的空间。你可能不知道 react native 遇到错误的写法有多爆炸,app 会直接崩溃。不是所有人都能精通 react,总会有一两个愣头青会这么干。
azcvcza
2021-02-20 16:05:17 +08:00
只能说,js 的 async 函数虽然叫做异步函数,但实际上只是 Promise 的语法糖,和一般人认为的,都叫函数有啥不一样,有很大区别,比较容易入坑
tedd
2021-02-20 16:05:27 +08:00
直接跟 .then 吧

useEffect(async () => {
loadContent().then(res => {});
}, []);
tedd
2021-02-20 16:05:59 +08:00
忘去了 async 👆
anjianshi
2021-02-20 16:07:36 +08:00
我觉得 useEffect() 可能有个潜在逻辑:第二次触发 useEffect 里的回调前,前一次触发的行为都执行完成,返回的清理函数也执行完成。这样逻辑才清楚。

useEffect(() =>{
doSomething()
return () => clear()
}, [var])

同步情况下,var 变化触发 useEffect() 时,前一次的 doSomething() 和 clear() 肯定都执行完成了。

useEffect(async () =>{
await doSomething()
return () => clear()
}, [var])

而如果是异步的,情况会变得很复杂,可能会很容易写出有 bug 的代码。
所以它不直接支持,如果用户确定这个异步操作与 useEffect() 的多次触发没有冲突,就自行封装。
wxsm
2021-02-20 16:15:19 +08:00
@tedd 你不觉得很丑陋吗,别的地方都用 async 这里来个 then
wxsm
2021-02-20 16:17:12 +08:00
@azcvcza 说是这么说,但是 async 的初衷就是让异步有同步的使用体验,useEffect 明显违背了这个初衷。
zhuweiyou
2021-02-20 16:22:08 +08:00
你返回了 promise, 它当销毁函数处理了.

不得不说, 确实 sb.
wxsm
2021-02-20 16:29:30 +08:00
@anjianshi 道理是这么个道理,但复杂就不做,最终不是恶心了用户吗
iahu
2021-02-20 16:30:09 +08:00
我的理解是 hook 在每次 rerender 时都会生成一个快照,而且这些快照之间的数据结构是链式的。如果 useEffect 的 callback 是 Promise 类型的,那所有链接上的 hook 必将都得是异步的,而 FC (functional component) 是同步的,在同步的 FC 里想拿异步的状态就完全是另一套操作了。

FC 和 hook 的搭配是函数式( FP )编辑思想,FP 构建是纯函数之上的。之所以不能内部实现就是与设计思想不符,而且实现了就不能只在这一个地方实现,到处都得支持。
sm0king
2021-02-20 16:46:36 +08:00
看下 hooks 的实现原理或许可以解决你的疑问。
noe132
2021-02-20 16:46:44 +08:00
因为 useEffect 每次渲染都会执行,每次执行都要执行上次执行返回的 teardown.

了解一下我针对 mobx 封装的 react-composition-api
https://github.com/Firefox-Pro-Coding/react-composition-api
用 react+mobx 的同学可以试试

参考了 vue3 api,可以 onMounted onUnmounted 嵌套,支持 async,支持 reactivity watcher 自动 teardown,减少一大堆模板代码,极大提高开发舒适度。目前已经用在我们公司线上项目里了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/754630

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX