V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
wxsm
V2EX  ›  程序员

useEffect 为什么不能支持 async function?

  •  2
     
  •   wxsm ·
    wxsms · 2021-02-20 15:27:36 +08:00 · 6449 次点击
    这是一个创建于 1370 天前的主题,其中的信息可能已经有所发展或是发生改变。
      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 偷懒,还是我傻逼?

    第 1 条附言  ·  2021-02-20 23:30:51 +08:00
    再强调一下:

    我知道 useEffect 为什么不能用 async,大家回复前请看帖。

    我想知道的是,React 为什么要这么设计。

    大家经常说 React 是大道至简,但是在我看来,这种 useEffect 就像 express,能用,但是丑陋。而我期待的是 koa2 这种级别的,极致的代码艺术。我觉得 React 这种级别的库,有理由将对 async 的支持再上一层楼。
    74 条回复    2022-11-01 14:13:31 +08:00
    weixiangzhe
        1
    weixiangzhe  
       2021-02-20 15:41:53 +08:00
    这样写也挺危险的, 建议
    weixiangzhe
        2
    weixiangzhe  
       2021-02-20 15:41:59 +08:00
    weixiangzhe
        3
    weixiangzhe  
       2021-02-20 15:44:29 +08:00   ❤️ 1
    主要是 用 async,也相当于这样吧

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

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

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

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

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

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

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

    FC 和 hook 的搭配是函数式( FP )编辑思想,FP 构建是纯函数之上的。之所以不能内部实现就是与设计思想不符,而且实现了就不能只在这一个地方实现,到处都得支持。
    sm0king
        19
    sm0king  
       2021-02-20 16:46:36 +08:00
    看下 hooks 的实现原理或许可以解决你的疑问。
    noe132
        20
    noe132  
       2021-02-20 16:46:44 +08:00 via Android
    因为 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,减少一大堆模板代码,极大提高开发舒适度。目前已经用在我们公司线上项目里了
    wxsm
        21
    wxsm  
    OP
       2021-02-20 16:52:32 +08:00
    @noe132 可惜我司的项目已经被 redux 套牢了
    wxsm
        22
    wxsm  
    OP
       2021-02-20 16:57:11 +08:00
    @iahu 第一,hooks 目前并不是全部要求同步的,至少 useCallback 可以直接定义 async 函数。第二,useEffect 现在的做法也只能是假同步,要求别人在 effet 里必须写纯函数,然而又建议有异步需求的人在纯函数里面写 IIFE,这种做法也同样无法保证销毁函数与 effect 函数的时序性。这就是单纯的简化了自己的工作,把困难的工作交给了用户。
    Kasumi20
        23
    Kasumi20  
       2021-02-20 17:19:23 +08:00
    useEffect 需要立即同步地返回一个清除函数,你为了用一个 await 语法,返回一个 Promise 给 React 干嘛
    joesonw
        24
    joesonw  
       2021-02-20 17:24:55 +08:00
    useEffect(fn: () => () => void).

    useEffect 是要返回一个函数() => void 的, 作为 unmount 的时候回调用的. async 返回的话就变成 () => Promise<void>了. 签名不对
    iahu
        25
    iahu  
       2021-02-20 18:07:09 +08:00   ❤️ 2
    - useCallback 的签名是 `(a, b) => a` 第一个参数其实并不是 callback,在 useCallback 函数里不会被执行,任何时候传入相同参数都会返回相同的结果,它是符合纯函数的规范的。
    - useEffect 属性 effect hook 。它的执行和销毁过程比较特殊,下执行时会清除上一次的状态,在同步的渲染逻辑里,怎么保证能清除异步的状态?
    soulmt
        26
    soulmt  
       2021-02-20 18:21:12 +08:00   ❤️ 2
    用 aysnc 标记的话,这个函数就是分步执行了,这样的话在不报错的情况下,react 可能无法同步拿到 return 的函数,所以无法控制订阅的卸载导致代码不可控。在 react 的理念中,不可控是非常危险的操作,所以宁愿让代码恶心一点。
    就比如你想在 return 里面卸载监听,但是 react 因为 await 的问题,都不知道你什么时候才能执行到 return,所以在执行到 return 这段时间内,你的所有操作都会造成不确定的结果。这是我想到的一个原因。
    wxsm
        27
    wxsm  
    OP
       2021-02-20 18:25:16 +08:00 via iPhone
    @iahu 所以说心智负担太重,写个代码跟搞科研一样
    cattchen
        28
    cattchen  
       2021-02-20 18:26:33 +08:00 via iPhone
    此外,用这种写法后,类型签名不一致了,原本应该返回一个 Function 用来处理 unmount,现在变成了 Promise <TValue>
    otakustay
        29
    otakustay  
       2021-02-20 18:27:27 +08:00   ❤️ 7
    因为 React 的生命周期不是你控制的,严格来说是用户控制的(用户要离开你不能阻止他)
    而对于 useEffect,在生命周期结束(或 deps 变化)时,副作用需要被清理
    并且对于一个 async 的过程,如果它没有完成(未 resolve 也未 reject ),那么从理论上来说,在 effect 清理时它是**必须**被中断的,因为 async 过程完成后就会发生副作用,而此时 effect 已经结束了清理,这个副作用一但发生就会失控
    所以,React 很简单地要求 useEffect 不能直接用 async 函数,来促使你处理 async 的中断逻辑

    但很少有人理解到这个点,不但不处理 async 让异步过程随意泄露不可控,还怪 React 的 API 设计
    wxsm
        30
    wxsm  
    OP
       2021-02-20 18:27:51 +08:00 via iPhone
    @soulmt 其实这个我都懂。只不过我在想的是,为什么这样。比如卸载函数为什么是 effect 的返回值,而不是独立的一个参数
    soulmt
        31
    soulmt  
       2021-02-20 18:31:14 +08:00   ❤️ 1
    这也比较好理解,因为闭包,你注册的作用域和销毁的作用域要保持一致,用参数控制的话,一方面是作用域问题,还有就是参数来销毁不能保证结果,就比如你注册的函数可能调用失败了,但是销毁的函数并不知道,你们之间也没有办法在执行层面进行状态共享。
    otakustay
        32
    otakustay  
       2021-02-20 18:32:27 +08:00   ❤️ 3
    我给一个官方说法的指引吧: https://overreacted.io/a-complete-guide-to-useeffect/#speaking-of-race-conditions

    副作用的清理和 rece condition 的应对是一个道理,都是 async 要在 effect 退出时中止(或至少告诉后续的副作用不要发生)
    weimo383
        33
    weimo383  
       2021-02-20 20:29:28 +08:00
    @wxsm 网络请求的话请用 SWR,hook 超好用
    cwliang
        34
    cwliang  
       2021-02-20 21:32:21 +08:00
    能问出这个问题,说明你还需加强学习
    yuang
        35
    yuang  
       2021-02-20 21:32:21 +08:00 via Android
    那个异步函数可以条件执行
    ericls
        36
    ericls  
       2021-02-20 21:34:18 +08:00 via iPhone
    Promise 没学好不要写 async .
    mdn
        37
    mdn  
       2021-02-20 23:13:11 +08:00
    useEffect 是最基础的 hook,
    useAsyncEffect 可以自己封装

    //github.com/rauldeheer/use-async-effect
    wxsm
        38
    wxsm  
    OP
       2021-02-20 23:14:59 +08:00 via iPhone
    @cwliang
    @ericls
    可不就是在向各位学习吗。楼上很多回复还是很认真地回答了我的问题的。
    KuroNekoFan
        39
    KuroNekoFan  
       2021-02-20 23:23:25 +08:00 via iPhone
    点进来好几次
    最后觉得从实际操作上来说只要判断一下 useeffect 的返回值是不是 promise 就好,至于为什么不这样做,可能有些风格和个人偏好的原因吧……
    claneo
        40
    claneo  
       2021-02-20 23:32:18 +08:00   ❤️ 1
    这个帖子真的是有一半的人只看了标题就开始回答了。。。

    我感觉 React 的理念就是这样,尽可能只提供基础的 API,剩下的部分鼓励用户去封装。

    而且万一将来 React 支持异步渲染呢?说不定到时候 Promise 就有用了。
    wxsm
        41
    wxsm  
    OP
       2021-02-20 23:37:14 +08:00 via iPhone
    @claneo 你算是说到点上了,说到底还是因为 render 只支持同步执行。
    wxsm
        42
    wxsm  
    OP
       2021-02-20 23:39:18 +08:00 via iPhone
    @KuroNekoFan 这个楼上很多大牛回答得很好,它不是单纯判断一下 promise 就能成的事。
    leelz
        43
    leelz  
       2021-02-21 06:27:27 +08:00
    leelz
        44
    leelz  
       2021-02-21 06:28:22 +08:00   ❤️ 1
    楼上很多回答都在解释为什么不能用,而楼主的疑惑是为什么这么设计。。
    KuroNekoFan
        45
    KuroNekoFan  
       2021-02-21 07:40:38 +08:00 via iPhone
    @wxsm 我本来想说“fiber 做为最小执行单元,内部的东西必须是同步的”,但是事实上支持 async 函数直接作为参数和在函数内再用 iife+async 确实就是一个判断的事吧,因为按现在的约束来说有意义的返回是一个函数类型,而函数类型又是可以方便而明确的跟 promise 区分开来的
    wanghaoipv6
        46
    wanghaoipv6  
       2021-02-21 08:18:06 +08:00 via Android
    应该是个设计上的取舍?如果开了「返回值是 Promise,就忽略」这个口子,那很多人就会想「为啥不把其他类型也顺便忽略了?」,然后大家就随便返回,慢慢开始忘记有「返回销毁函数」这件事。这个代码就变成了屎山。

    我觉得看 React 可以把它想成是一个没有语言倾向,随时可能用 rust 重写的东西。react 的设计者兴趣点也不在 JavaScript 上(否则就会像 vue 那样狂用 proxy 了)。这种视角下你提到的「 koa2 极致的代码艺术」就变得非常的不优雅。(我猜的,我没怎么用过 koa2,不太清楚代码艺术具体指什么,不过我猜想是强依赖于 JavaScript 本身的特性发展出来的语法糖?)。

    因此,「不管类型,在运行时检查来确定代码行为」这种 JavaScript 味很重的东西,在 typescript 风潮愈演愈烈的,react 野心越来越大的当下,是一件根本就不被考虑的妥协。

    至于你上面提到的「卸载函数为什么是 effect 的返回值,而不是独立的一个参数」,这个实在是太丑了,更不可能考虑。
    wxsm
        47
    wxsm  
    OP
       2021-02-21 10:34:26 +08:00
    @leelz 看到你的回复还挺惊喜的,然而点进去看了一下说实话我挺失望的。说白了就是 React 也开始教你写代码了,发明的东西越来越多,并不能利用好 js 本身的特性。
    wxsm
        48
    wxsm  
    OP
       2021-02-21 10:44:02 +08:00
    @wanghaoipv6

    > 我觉得看 React 可以把它想成是一个没有语言倾向,随时可能用 rust 重写的东西

    俗话说干一行爱一行,有没有倾向暂且不谈,既然做了就要把它做好吧。至于 ts,它跟 js 也没有冲突,ts 本身就是一种对 js 的妥协,否则它干嘛不自立门户,要做 js 的超集呢。
    wxsm
        49
    wxsm  
    OP
       2021-02-21 10:57:00 +08:00
    @KuroNekoFan

    > 但是事实上支持 async 函数直接作为参数和在函数内再用 iife+async 确实就是一个判断的事吧

    还真不是一个判断的事。React 想要的是执行完函数立马得到销毁函数,如果加上了 async 这件事就无从谈起了。React 无法及时得到销毁函数,就无法及时销毁组件,整个架构立马复杂度倍增。

    至于 IIFE,React 对这件事的态度就是:我知道很多情况有这种需求,你们可以用 IIFE 来实现,至于发生了什么事我不管,我只负责创建和销毁,你们开发者用了异步记得自己把屁股擦干净就行了。
    KuroNekoFan
        50
    KuroNekoFan  
       2021-02-21 11:33:09 +08:00
    @wxsm 这很简单啊,对返回 promise 的当成跟 void 的一样处理就好了
    那么问题就是
    我允许 async 关键字,用户写起来方便一点,但多加一个针对性的约束
    还是我不允许 async 关键字,用户写起来麻烦一点,但是少一些约定 /约束
    wxsm
        51
    wxsm  
    OP
       2021-02-21 12:22:15 +08:00 via iPhone
    @KuroNekoFan 两种情况都会成为约束,不允许 async 这件事本身就是一个约束
    zed1018
        52
    zed1018  
       2021-02-21 12:35:35 +08:00
    不揣测 fb 这么设计的用意,帖子里的这种异步获取数据并更新界面的方案,我一直都是用 dispatch()做的。
    jinliming2
        53
    jinliming2  
       2021-02-21 13:13:48 +08:00   ❤️ 1
    如果你在 useEffect 里写了一个 async 函数:
    useEffect(async () => {
    await waitFor10Seconds();
    return () => cleaningUp();
    }, [dep]);
    这样,在这个 async 函数中等待了 10 秒才会返回,而这之间你触发了 dep 的更新,请问现在的执行逻辑会怎样?
    是整个组件卡着不动,等这个 Promise resolve 之后再去执行它的 cleaning up 函数吗?还是说这个 cleaning up 就不执行了?还是说把这个 cleaning up 函数加入队列,之后可能会乱序执行?或者排序后顺序执行?有时这个 Promise 也许永远不会 resolve 。
    这样就会产生开发过程中的歧义。
    默认约定返回 promise 的话就不支持 cleaning up ?但这就和 useEffect 本身的设计理念产生了冲突,本身的概念很简单,这又加了一种特例情况。

    按照我的习惯的话,这种异步任务不会写到 useEffect 里,而是写道外面,useEffect 中只是去调用这个函数:
    const fetchData = async () => {};
    useEffect(() => {
    fetchData();
    return () => abortFetch();
    }, [dep]);

    另外 Promise 未处理的警告,我这里默认是没有这个警告的,我也不会去配置这个警告。
    我觉得这是很正常的事情,Promise 作为一个返回值,它与其他的 return 1 、return "1" 有什么区别?在没有必要的情况下,其他的返回值你可以不接收、不处理,那为啥在没有必要处理的时候,要特别去关注 Promise 的处理呢?仅仅因为它是个“return new Promise()”?
    wxsm
        54
    wxsm  
    OP
       2021-02-21 15:50:56 +08:00
    @jinliming2

    > 按照我的习惯的话,这种异步任务不会写到 useEffect 里,而是写道外面,useEffect 中只是去调用这个函数

    其实现在我们很多也都是这么写的,另外提醒一下你的代码不严谨,fetchData 必须用 useCallback 否则无限循环。

    你的回答基本是基于”React 现在就是这样的”,然后提出了一系列反问。但是我觉得这种回答没什么建设性。我知道按照 React 现在的模式 async effect 走不通,我目前也提供不了更好的设计,只是我认为这不对,不够优雅,对于 React 这样一款追求大道至简的框架(库)来说,不契合。
    no1xsyzy
        55
    no1xsyzy  
       2021-02-21 16:33:35 +08:00
    @azcvcza async/await 模型就是 Future/Promise 的语法糖,跟 js 没太大关系,Python Rust 这两个也是这么玩的。
    建议自己实现一个 Actor 模型(

    我觉得 #13 的情况可能性比较大,不是复杂,而是根本执行模式上的冲突
    话说 JS 的 Promise 可以 cancel 吗?
    还是建议 Actor 模型(反正 stackfull 的没法在 JS 里实现只能模拟),Actor 模型才是真异步,连 Future 都没有,shot and gone

    话说,计算机方面,但凡听说“大道至简”,这个人肯定是在说 worst is better
    winglight2016
        56
    winglight2016  
       2021-02-21 18:07:38 +08:00
    我是写 RN 的时候了解 react 的,我记得 useEffect 的目的是为了刷新 UI,然后调用的是 useContext 里的方法,这些方法是可以定义为 async 的,我也不理解为什么直接调用就不行。如下:
    const {
    state: {currentObj, electricity},
    fetchElectricity,
    } = useContext(EnergyContext);

    useEffect(() => {
    console.log('selectedDate: ' + selectedDate.format());
    fetchElectricity(currentObj, timeType, selectedDate.valueOf());
    }, [currentObj, selectedDate]);

    const fetchElectricity = dispatch => async (currObj, timeType, timeValue) => {
    ...
    这么定义我觉得虽然略为烦琐,但是封装得还不错了,而且所有业务逻辑都统一在 Context 里实现,除了个别需求都可以用近似的代码实现,可读性也挺好的。
    aguesuka
        57
    aguesuka  
       2021-02-21 19:04:19 +08:00 via Android   ❤️ 1
    返回销毁函数就不是个好设计。
    EridanusSora
        58
    EridanusSora  
       2021-02-22 01:33:08 +08:00 via Android
    看了楼上的讨论,觉得还是返回销毁函数这个设计阻止了 async effect 的用法。
    那么返回销毁函数是不是合理的?虽然使用上感觉很精巧,但是似乎有点 trick 的感觉?
    azcvcza
        59
    azcvcza  
       2021-02-22 10:11:24 +08:00
    @no1xsyzy JS 默认的 Promise 没有 cancel,第三方实现的我看到过,但是各个 JS 引擎都没在正式实现加上
    soulmt
        60
    soulmt  
       2021-02-22 10:26:33 +08:00
    @leelz 设计就是为了让开发者按照设计的规范使用,通过用法限制去反推设计,并不矛盾,如果说有些用法是设计者没想到的,我相信这种事情在 react 里面应该会当作 bug 修复,或者会被认为是合理的。
    soulmt
        61
    soulmt  
       2021-02-22 10:33:09 +08:00
    @aguesuka 我觉得挺好的,原地销毁可以解决作用域的问题,把同一件事情的整个周期放在了一起处理,这个有助于代码可读性和维护性。
    SmiteChow
        62
    SmiteChow  
       2021-02-22 10:59:17 +08:00
    可以用,我一直都在用,所以楼上各位使我非常疑惑。
    wxsm
        63
    wxsm  
    OP
       2021-02-22 13:21:41 +08:00
    @soulmt js 对于 async 的设计就是让异步函数和同步函数具有同等的书写体验,作为一款(至少目前来说)基于 js 的库,React 首先就没有尊重这个设定。
    wxsm
        64
    wxsm  
    OP
       2021-02-22 13:23:02 +08:00
    @SmiteChow 如果你一直都是这么用的,那我建议你赶紧去认真学一学,改正过来。
    soulmt
        65
    soulmt  
       2021-02-22 14:09:33 +08:00
    @wxsm 那我可得杠一把了哈哈,react 是基于 js 没错,但是基于 js 怎么去定义开发方式,这个其实没有必要一定去遵守哈,就好比 react 当年在处理传递函数的时候是否要自动 bind(this)还是开发者手动 bind,也是因为那极小的一部分场景不兼容,而造成了开发者需要不厌其烦的手动 bind,又或者在定义 getDerivedStateFromProps 采用了纯函数的方式(内部访问不了 this,只能接受入参),这其实对于 class 组件的定义来说,也是"非常规"定义,但是这背后的含义,只有定义的人才知道,所以 hooks 也没有不尊重这个设定,起码允许在内部,开发者可以为所欲为的。
    no1xsyzy
        66
    no1xsyzy  
       2021-02-22 15:03:27 +08:00
    @wxsm 从书写体验这一点上来说,async 必然导致关键词污染……
    话说 React 的函数式组件本身支持 async 定义吗?
    wxsm
        67
    wxsm  
    OP
       2021-02-22 15:30:50 +08:00
    @soulmt 没有说一定要遵守,我的出发点是「优雅」。当开发体验与原生 js 的契合度越高,发明的东西越少,需要写的代码越少,我认为越优雅。不是说 React 定义的 hooks 方式不好,而是说,它目前存还存在这些不足,导致它还不够优雅。你说的手动 bind 确实丑陋,但是 React 现在不也通过 hook 把它干掉了吗,就算不写 hook 至少现在可以在类上面定义箭头成员函数,再也不用写 bind 了,这就是进步。
    wxsm
        68
    wxsm  
    OP
       2021-02-22 15:34:55 +08:00
    @no1xsyzy

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

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

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

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

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

    你说的调用另外一个函数(假设 B)来销毁,这一点就遇到一个问题,比如监听,你在 Effect 里面注册了监听 /定时器,那么你卸载的时候别的函数是访问不了 Effect 里面的变量的,那你监听的回调函数得上抛到 B 的作用域里面,这样的话,Effect 所带来的独立作用域就被打乱了,也不符合 hooks 的心智模型,这在 class 组件里面也是一个用起来很麻烦的事情。
    SmiteChow
        71
    SmiteChow  
       2021-02-23 09:45:17 +08:00
    @wxsm 改啥?我能 work 啊
    myCupOfTea
        72
    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
        73
    myCupOfTea  
       2021-02-23 17:04:07 +08:00
    async 的传染性太强了,除非整个 render 全部异步化重写一遍
    yetrun
        74
    yetrun  
       2022-11-01 14:13:31 +08:00
    正好访问到这个帖子,我来简短地表达一下我的观点。

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

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

    2. useEffect 不支持传递异步函数不是一个很好的设计,改成如上,支持传递异步函数。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1210 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 23:11 · PVG 07:11 · LAX 15:11 · JFK 18:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.