react 新手关于 react useEffect 的困惑,为什么 useEffect 里面的 cleanup 函数里面的 props 是旧的,如何从源码解释? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ooo4
V2EX    React

react 新手关于 react useEffect 的困惑,为什么 useEffect 里面的 cleanup 函数里面的 props 是旧的,如何从源码解释?

  •  2
     
  •   ooo4 279 天前 4326 次点击
    这是一个创建于 279 天前的主题,其中的信息可能已经有所发展或是发生改变。

    版本: [email protected]

    我看源码就是先 UnmountEffects 后 MountEffects,里面也只是递归遍历而已,为什么 cleanup 里面的 props 是上一次的了?

    commitPassiveUnmountEffects(root.current); commitPassiveMountEffects(root, root.current, lanes, transitions); 
    // 复现的 demo function App() { const [num, setNum] = useState(100) window.__setNum = setNum return <Comp num={num}></Comp> } function Comp(props) { debugger useEffect(() => { debugger props // {num:1000} return () => { debugger // 为什么这里是旧的 props? {num:100} props } }, [props.num]) return ( <p> <span>{props.num}</span> </p> ) } setTimeout(() => { __setNum(1000) }, 1000) 
    47 条回复    2025-01-06 14:01:45 +08:00
    shintendo
        1
    shintendo  
       279 天前   1
    闭包
    lisongeee
        2
    lisongeee  
       279 天前
    新状态组件的 useEffect 和旧状态组件的 cleanup 同时被你 debugger 到了
    ooo4
        3
    ooo4  
    OP
       279 天前
    @shintendo 从 chrome 的 debugger 工具看,这个 props 确实是来源于闭包,thanks!!
    ltaoo1o
        4
    ltaoo1o  
       279 天前
    我这里也有一个闭包的问题,https://codesandbox.io/p/sandbox/zuo-yong-yu-de-wen-ti-kvt3jp?file=%2Fsrc%2FApp.js

    react 的函数组件让人恼火,各种潜在的问题
    Razio
        5
    Razio  
       279 天前
    @ltaoo1o #4 你比 OP 还新手
    ltaoo1o
        6
    ltaoo1o  
       279 天前
    @Razio 可以详细说说吗,不要只输出态度..
    Torpedo
        7
    Torpedo  
       279 天前   1
    @ltaoo1o #4 就是闭包的问题。每次组件状态变化,函数都会执行一次。所以最新的状态都在最新的一次执行里。但是这里你 keydown 监听的是第一次函数运行的函数。那个函数的闭包上下文里,state 是最初的。

    新手不要直接用 useEffect 。找个 react-use ahook 啥的。用封装好的 hook 。无论什么水平,写的好的话,需要手写 useEffect 的很少
    shintendo
        8
    shintendo  
       279 天前
    @ltaoo1o 你这个我觉得无解,可行的解法你自己注释里都写了
    或者干脆就从 dom 里拿 v
    hyh0u0
        9
    hyh0u0  
       279 天前
    (变量)代码在写下来的时候就被捕获了。或者说,在这个函数的生命周期里,函数内部的那个 props 就只有传入的 props
    TWorldIsNButThis
        10
    TWorldIsNButThis  
       279 天前 via iPhone
    @ltaoo1o 注释是啥意思,正确方式就是 ref 或者让 effect 依赖 v 啊
    ltaoo1o
        11
    ltaoo1o  
       279 天前
    当时出问题的时候,我就意识到是闭包问题了,百分之九十九函数组件的问题就是闭包问题

    我这个代码,从语义上来说非常简单也很实际,「页面初始化后监听回车事件」,用函数组件就是写不出来,用类组件就没有这种问题。
    所以我现在写代码都少用 hook ,它改变了代码作为领域知识的意义,变成为框架去改变业务含义,导致现在都是在写框架代码,不是写业务代码。
    ltaoo1o
        12
    ltaoo1o  
       279 天前
    @TWorldIsNButThis 我希望代码表达正确的「语义」
    ltaoo1o
        13
    ltaoo1o  
       279 天前
    @shintendo #8 是的,我想了很久,无解
    ltaoo1o
        14
    ltaoo1o  
       279 天前
    @Torpedo 感谢
    shintendo
        15
    shintendo  
       279 天前
    @ltaoo1o 你说 v 不能放 useRef 里,那可以把 log 放 useRef 里
    https://codesandbox.io/p/sandbox/zuo-yong-yu-de-wen-ti-forked-pn425w
    TWorldIsNButThis
        16
    TWorldIsNButThis  
       279 天前 via iPhone
    @ltaoo1o
    class 组件可以是因为 this 就是 ref 啊
    ltaoo1o
        17
    ltaoo1o  
       279 天前
    @shintendo #15 起作用的是这个刷新 log 函数的代码吧,而且 log 可能是一个比较复杂的函数,它要拿很多个状态进行处理,这里就要依赖 v1 v2 v3 等等,容易漏

    ```
    useEffect(() => {
    logRef.current = () => {
    alert(v);
    };
    }, [v]);
    ```
    ltaoo1o
        18
    ltaoo1o  
       279 天前
    @TWorldIsNButThis #16 所以我说函数组件有额外的心智负担,容易出问题,感觉在和框架斗智斗勇
    ljpCN
        19
    ljpCN  
       279 天前
    @ltaoo1o 你应该在 input 标签的 onKeyDown 回调里处理你的键盘监听,而不是在 useEffect 里处理
    ltaoo1o
        20
    ltaoo1o  
       279 天前
    @ljpCN 实现方式其实很多种,难的是保留语义的前提下。我希望我的代码,别人一看,就能明白是「当页面加载后,监听回车事件并 xxx 」,如果写在 input 标签,就变成了「当这个 input 回车时,xxx 」,这里的语义就丢失了。
    当然代码能跑就行,「代码表达语义」仅仅是我个人的追求。
    ljpCN
        21
    ljpCN  
       279 天前   1
    @ltaoo1o 那给你看看我改完的代码吧,个人觉得比你的语义更清晰。https://codesandbox.io/p/sandbox/zuo-yong-yu-de-wen-ti-forked-6ljjys
    ltaoo1o
        22
    ltaoo1o  
       279 天前
    @ljpCN #21 额,语义是指业务逻辑,就比如我必须表达出「当页面加载后,监听回车」,在这个前提下,如何完成需求,你的代码非常好,没有问题。只是少了「当页面加载后」的这个含义。
    ljpCN
        23
    ljpCN  
       279 天前
    @ltaoo1o 如果你是想要在整个页面监听回车按键,先不讨论这个需求的合理性,为了实现你要的语义清晰,你应该寻求对 hooks 的封装来实现你的 log 函数拿到最新的 state ,或者直接通过 ref 获取 input 标签当前的 value 。前者的话举一个例子: https://ahooks.js.org/hooks/use-memoized-fn
    shintendo
        24
    shintendo  
       279 天前
    @ljpCN 你这个跟他的不一样,他监听了 document ,在输入框外面 enter 也能触发
    shintendo
        25
    shintendo  
       279 天前
    @ltaoo1o 容易漏是指漏依赖? eslint 可以检查啊
    ltaoo1o
        26
    ltaoo1o  
       279 天前
    @shintendo #25 心智负担,而且代码会比较丑
    ljpCN
        27
    ljpCN  
       279 天前
    @shintendo 嗯,我上面也说了要想全局监听的解决方案。另外我主要想表达的是 hooks 带来的心智负担不足以成为因噎废食的理由。 @ltaoo1o 这位老哥所说的“用函数组件就是写不出来”是不成立的;“hook 改变了代码作为领域知识的意义,现在都是在写框架代码,不是写业务代码”则缺少论证看起来是个人偏见。
    LOWINC
        28
    LOWINC  
       279 天前
    你这个前提就有问题
    “1. 该代码表示「当页面初始化后」,所以这里不能依赖 log 、handleKeyDown 、”

    可以看下 dan 的文章 https://overreacted.io/a-complete-guide-to-useeffect/

    @ltaoo1o
    ltaoo1o
        29
    ltaoo1o  
       279 天前
    @LOWINC 我理解你的意思,useEffecet(fn, []) 不能表达「当页面初始化后」,和 componentDidMount 不同,这也是我无语的一点,函数组件没有一个明确的函数、方式来表达「当页面、组件初始化后」。
    现在社区普遍都将 useEffecet(fn, []) 作为「当页面初始化」的含义来用不是吗,如果不这么写,可以给我一个方案吗,我确实不懂该如何写
    dango33
        30
    dango33  
       278 天前
    @ltaoo1o #4 https://zh-hans.react.dev/learn/separating-events-from-effects
    建议重新看一遍 React 的教程,把有些重要问题解释得很清楚了。
    ltaoo1o
        31
    ltaoo1o  
       278 天前
    @dango33 完整看完了,这个教程不是证明了我说的吗,后面提到了 useEffectEvent ,一个「还没有发布的实验性 API 」,如果我用 useEffectEvent 包 log 就能解决我的问题,为什么要发布一个新的 hook ,就是有一些场景用 useEffect 解决不了。
    另外,如果希望讨论,可以把你的观点明确地表达出来,我承认自己很菜,也欢迎讨论。
    dango33
        32
    dango33  
       278 天前
    @ltaoo1o #31 “建议你读教程”并不是在尝试讽刺你菜,闻道有先后,不是说只有菜鸟才看教程。如果还没有读过,建议快速过一遍,尤其是对于从 class 时代过来的人。主要是能够(在一定程度上)避免踩坑,不至于对你提出的这个问题感到恼火(因为这并不是现阶段很难发现和解决的问题)。

    如果对#15 的方式不满意,也可以选择把你原来的 log 函数扔给 ref ,这样更新 ref 的 effect 就只有一个依赖了。
    或者用组件外的一个实例记录要上传的数据,提供暂存和上传数据的方法给组件用,这样 useEffect 就完全没有依赖了。(一点拙见)
    ltaoo1o
        33
    ltaoo1o  
       278 天前
    @dango33 #32 我后面也说了,当我发现不对时,我就意识到哪里有问题,应该怎么写。但任何写法,都违背了我希望用代码表达的含义。其次,一个需要专门写文章来说明,并且还要发布新的 API 来支持更多场景的坑,不让人恼火吗,我写业务就够累了还要处处小心这种坑。
    另外,我现在所有代码都是像你说的另外起实例,很少用 hook 了,像这样

    ```js
    function HomePage() {
    const [state, setState] = useState($page.state);
    useEffect(() => {
    $page.onStateChange((v) => setState(v));
    $page.ready();
    }, []);
    return ();
    }
    ```

    这样写就不用考虑框架的坑了,有问题也是我自己的问题了。
    zhengfan2016
        34
    zhengfan2016  
       278 天前
    第一次见到 react.fc 外面使用 settimeout ,黑马程序员也不是这么教的吧,这么写肯定容易出问题。就和不用 const 偏要用 var 一样
    dango33
        35
    dango33  
       278 天前
    @ltaoo1o #33 函数组件整个一大闭包,所以这些“坑”对于我来说反而挺符合直觉的 XD 。
    ooo4
        36
    ooo4  
    OP
       278 天前
    @zhengfan2016 因为我在调试 react 源码,不想通过合成事件去触发,徒增额外调试
    demonzoo
        37
    demonzoo  
       278 天前
    ltaoo1o
        38
    ltaoo1o  
       278 天前
    @demonzoo 哥,感谢你,可是不是我想要的 我在注释里写了不能依赖 handleKeydown 等,不然语义就变了,变成了「当 handleKeydown 」改变时,监听 keydown 事件。但是我希望表达的是「当页面加载后,监听事件」
    ljpCN
        39
    ljpCN  
       277 天前
    @ltaoo1o 不知道你是没看到还是无视了我上面发的消息,既然你还是认为没有解决方案,我只能把解决方案写好发出来了: https://codesandbox.io/p/sandbox/zuo-yong-yu-de-wen-ti-forked-rqm5jy
    ljpCN
        40
    ljpCN  
       277 天前
    @ltaoo1o 另外如果你真的接受了 react 文档中关于 useEffect 的定义,你应该知道 useEffect 的语义并非是 xx 发生变化时执行 yy 。useEffect 只是一个渲染过程的副作用,在严格模式下即使组件只挂载一次它也会执行两次。你不能把 useEffect 当做监听状态变化的回调来使用,而是应当作为每次渲染完成后的副作用来理解,只要你在 useEffect 的返回函数里对副作用做好适当的清理,你是不用去关心它执行了一次还是两次还是每次渲染后都执行的。当然你也可以用 useMemoizedFn 这样的 hook 来减轻你理解函数当前使用的是哪一次渲染的闭包变量的心智负担,这也完全没问题。
    ltaoo1o
        41
    ltaoo1o  
       277 天前
    @ljpCN #39 抱歉是没看到,你的实现很巧妙,很厉害。
    ltaoo1o
        42
    ltaoo1o  
       277 天前
    @ljpCN #40 确实是我太菜了,我很多框架、语言都写,无论 vue 、flutter 还是 swift 这些,都是有明确的 组件加载后 的语义的,我也按这个习惯来写,导致对 react 这里不熟悉,抱歉抱歉,忽略了你的消息。
    demonzoo
        43
    demonzoo  
       277 天前
    @ltaoo1o 哥,感谢你,可是不是我想要的 我在注释里写了不能依赖 handleKeydown 等,不然语义就变了,变成了「当 handleKeydown 」改变时,监听 keydown 事件。但是我希望表达的是「当页面加载后,监听事件」
    ================
    听哥们一句劝,其实我觉得其他人提到的在 input 里加 onKeyDown handler 是最好的方法,但你既然要用 useEffect 那我就帮你写了一个用 useEffect 实现的方法。
    40 楼的哥们说的对,你对 useEffect 的理解恐怕有点偏颇,而且我觉得你对语义的追求也有些极端,这跟 “茴字的几种写法” 有什么区别呢
    最后,如果你真的采用了 useMemoizedFn 这种解法,那我觉得真的是高射炮打蚊子了,本来几行的代码非要写成一百行,应用了一堆 useRef ,useMemo ,useEffect 等 hook ,本末倒置。。。
    ltaoo1o
        44
    ltaoo1o  
       277 天前
    @demonzoo #43 是的我承认我对 useEffect 的理解不对,语义这个算个人追求,它能让我在不同框架、语言,用一套思维去写前端,也能让代码有可迁移、可维护、更好理解,当然这也是个人追求,不这样做完全是可以的,尤其是公司项目,能写完就行。
    最后忘了怎么做的,是公司项目,代码比较复杂。我自己项目是抛弃了 hook ,只把 react 当视图渲染用,没有这些问题。
    geekris1
        45
    geekris1  
       277 天前
    @ltaoo1o #4 一进页面就挂了 addEventListener 这时候对应函数里已经形成闭包了 没次取的 state 都是绑定那一刻 state 的值。
    解决方法就是用 useRef, 写个新的 useEffect 监听 state 的变化,每次 state 更新同步更新 ref 的值 addEventListener 的函数里取 ref.current 这样就能保证每次取的都是最新的
    上述操作也有对应的 hooks 工具可以参考
    具体参考: https://ahooks.js.org/hooks/use-latest
    Dyon
        46
    Dyon  
       277 天前
    @ltaoo1o 正确的做法是使用 jsx 的 onKeydown ,不要用 useEffect
    realJamespond
        47
    realJamespond  
       277 天前
    useEffect 就是变化,return 就是变化前的回调,类似还有卸载前的回调
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5767 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 21ms UTC 06:14 PVG 14:14 LAX 23:14 JFK 02:14
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86