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 偷懒,还是我傻逼?

6815 次点击
所在节点    程序员
74 条回复
soulmt
2021-02-22 10:33:09 +08:00
@aguesuka 我觉得挺好的,原地销毁可以解决作用域的问题,把同一件事情的整个周期放在了一起处理,这个有助于代码可读性和维护性。
SmiteChow
2021-02-22 10:59:17 +08:00
可以用,我一直都在用,所以楼上各位使我非常疑惑。
wxsm
2021-02-22 13:21:41 +08:00
@soulmt js 对于 async 的设计就是让异步函数和同步函数具有同等的书写体验,作为一款(至少目前来说)基于 js 的库,React 首先就没有尊重这个设定。
wxsm
2021-02-22 13:23:02 +08:00
@SmiteChow 如果你一直都是这么用的,那我建议你赶紧去认真学一学,改正过来。
soulmt
2021-02-22 14:09:33 +08:00
@wxsm 那我可得杠一把了哈哈,react 是基于 js 没错,但是基于 js 怎么去定义开发方式,这个其实没有必要一定去遵守哈,就好比 react 当年在处理传递函数的时候是否要自动 bind(this)还是开发者手动 bind,也是因为那极小的一部分场景不兼容,而造成了开发者需要不厌其烦的手动 bind,又或者在定义 getDerivedStateFromProps 采用了纯函数的方式(内部访问不了 this,只能接受入参),这其实对于 class 组件的定义来说,也是"非常规"定义,但是这背后的含义,只有定义的人才知道,所以 hooks 也没有不尊重这个设定,起码允许在内部,开发者可以为所欲为的。
no1xsyzy
2021-02-22 15:03:27 +08:00
@wxsm 从书写体验这一点上来说,async 必然导致关键词污染……
话说 React 的函数式组件本身支持 async 定义吗?
wxsm
2021-02-22 15:30:50 +08:00
@soulmt 没有说一定要遵守,我的出发点是「优雅」。当开发体验与原生 js 的契合度越高,发明的东西越少,需要写的代码越少,我认为越优雅。不是说 React 定义的 hooks 方式不好,而是说,它目前存还存在这些不足,导致它还不够优雅。你说的手动 bind 确实丑陋,但是 React 现在不也通过 hook 把它干掉了吗,就算不写 hook 至少现在可以在类上面定义箭头成员函数,再也不用写 bind 了,这就是进步。
wxsm
2021-02-22 15:34:55 +08:00
@no1xsyzy

1. 从利弊关系来说,利大于弊。
2. 不支持,这应该是症结所在。
aguesuka
2021-02-22 17:58:12 +08:00
useEffect 的第一个参数的类型是

() => (() => void) | void // 在 ReactFiberHooks 文件

设计得不好的地方在于这个函数做了两件事情,它是一个有副作用的函数,而且是另一个有副作用的函数的生成器。但是语义上,它并不是销毁函数的生成器,而是在副作用的同时设置或者改变了销毁函数。

合理的思路应该是这个参数没有返回值,但是在执行过程中可以调用另外一个函数,相当于返回销毁函数。

@soulmt
soulmt
2021-02-22 18:35:13 +08:00
@aguesuka 设计返回函数的作用就是自己清理自己的副作用,关注点比较集中,不像 class 组件,一个地方注册了。需要在别的生命周期里面销毁,这对复杂页面的阅读性上有点不友好。

你说的调用另外一个函数(假设 B)来销毁,这一点就遇到一个问题,比如监听,你在 Effect 里面注册了监听 /定时器,那么你卸载的时候别的函数是访问不了 Effect 里面的变量的,那你监听的回调函数得上抛到 B 的作用域里面,这样的话,Effect 所带来的独立作用域就被打乱了,也不符合 hooks 的心智模型,这在 class 组件里面也是一个用起来很麻烦的事情。
SmiteChow
2021-02-23 09:45:17 +08:00
@wxsm 改啥?我能 work 啊
myCupOfTea
2021-02-23 17:02:39 +08:00
,useEffect 如果支持 async 同样会带来其他心智负担,干脆不支持,我觉得也没啥毛病

如果 useEffect 支持 async,cancel 怎么处理呢,根本就没有好的方法
useEffect(() => {
// Create an scoped async function in the hook
async function anyNameFunction() {
await loadContent();
}
// Execute the created function directly
anyNameFunction();
}, []);
你这么写也会有问题
你能保证下次 effect 进来上次的执行的结果完成了吗,如果后台有负载均衡器,你能保证前一次的调用一定在后一次执行的返回值前面吗
很可能出现 前一次返回的数据比后一次慢导致修改 state 的值是前一次的结果,干脆简单一点,复杂的情况各自自己封装就好
myCupOfTea
2021-02-23 17:04:07 +08:00
async 的传染性太强了,除非整个 render 全部异步化重写一遍
yetrun
2022-11-01 14:13:31 +08:00
正好访问到这个帖子,我来简短地表达一下我的观点。

1. useEffect 返回销毁函数在我看来不是一个很好的设计,改成这样就好:

```
useEffect(callback).clearup(clearup)
```

2. useEffect 不支持传递异步函数不是一个很好的设计,改成如上,支持传递异步函数。

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

https://ex.noerr.eu.org/t/754630

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

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

© 2021 V2EX