我看源码就是先 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)
![]() | 1 shintendo 279 天前 ![]() 闭包 |
![]() | 2 lisongeee 279 天前 新状态组件的 useEffect 和旧状态组件的 cleanup 同时被你 debugger 到了 |
![]() | 4 ltaoo1o 279 天前 我这里也有一个闭包的问题,https://codesandbox.io/p/sandbox/zuo-yong-yu-de-wen-ti-kvt3jp?file=%2Fsrc%2FApp.js react 的函数组件让人恼火,各种潜在的问题 |
![]() | 6 ltaoo1o 279 天前 @Razio 可以详细说说吗,不要只输出态度.. |
![]() | 7 Torpedo 279 天前 ![]() @ltaoo1o #4 就是闭包的问题。每次组件状态变化,函数都会执行一次。所以最新的状态都在最新的一次执行里。但是这里你 keydown 监听的是第一次函数运行的函数。那个函数的闭包上下文里,state 是最初的。 新手不要直接用 useEffect 。找个 react-use ahook 啥的。用封装好的 hook 。无论什么水平,写的好的话,需要手写 useEffect 的很少 |
9 hyh0u0 279 天前 (变量)代码在写下来的时候就被捕获了。或者说,在这个函数的生命周期里,函数内部的那个 props 就只有传入的 props |
10 TWorldIsNButThis 279 天前 via iPhone @ltaoo1o 注释是啥意思,正确方式就是 ref 或者让 effect 依赖 v 啊 |
![]() | 11 ltaoo1o 279 天前 当时出问题的时候,我就意识到是闭包问题了,百分之九十九函数组件的问题就是闭包问题 我这个代码,从语义上来说非常简单也很实际,「页面初始化后监听回车事件」,用函数组件就是写不出来,用类组件就没有这种问题。 所以我现在写代码都少用 hook ,它改变了代码作为领域知识的意义,变成为框架去改变业务含义,导致现在都是在写框架代码,不是写业务代码。 |
![]() | 12 ltaoo1o 279 天前 @TWorldIsNButThis 我希望代码表达正确的「语义」 |
![]() | 15 shintendo 279 天前 @ltaoo1o 你说 v 不能放 useRef 里,那可以把 log 放 useRef 里 https://codesandbox.io/p/sandbox/zuo-yong-yu-de-wen-ti-forked-pn425w |
16 TWorldIsNButThis 279 天前 via iPhone @ltaoo1o class 组件可以是因为 this 就是 ref 啊 |
![]() | 17 ltaoo1o 279 天前 @shintendo #15 起作用的是这个刷新 log 函数的代码吧,而且 log 可能是一个比较复杂的函数,它要拿很多个状态进行处理,这里就要依赖 v1 v2 v3 等等,容易漏 ``` useEffect(() => { logRef.current = () => { alert(v); }; }, [v]); ``` |
![]() | 18 ltaoo1o 279 天前 @TWorldIsNButThis #16 所以我说函数组件有额外的心智负担,容易出问题,感觉在和框架斗智斗勇 |
![]() | 20 ltaoo1o 279 天前 @ljpCN 实现方式其实很多种,难的是保留语义的前提下。我希望我的代码,别人一看,就能明白是「当页面加载后,监听回车事件并 xxx 」,如果写在 input 标签,就变成了「当这个 input 回车时,xxx 」,这里的语义就丢失了。 当然代码能跑就行,「代码表达语义」仅仅是我个人的追求。 |
21 ljpCN 279 天前 ![]() @ltaoo1o 那给你看看我改完的代码吧,个人觉得比你的语义更清晰。https://codesandbox.io/p/sandbox/zuo-yong-yu-de-wen-ti-forked-6ljjys |
![]() | 22 ltaoo1o 279 天前 @ljpCN #21 额,语义是指业务逻辑,就比如我必须表达出「当页面加载后,监听回车」,在这个前提下,如何完成需求,你的代码非常好,没有问题。只是少了「当页面加载后」的这个含义。 |
23 ljpCN 279 天前 @ltaoo1o 如果你是想要在整个页面监听回车按键,先不讨论这个需求的合理性,为了实现你要的语义清晰,你应该寻求对 hooks 的封装来实现你的 log 函数拿到最新的 state ,或者直接通过 ref 获取 input 标签当前的 value 。前者的话举一个例子: https://ahooks.js.org/hooks/use-memoized-fn |
27 ljpCN 279 天前 |
![]() | 28 LOWINC 279 天前 你这个前提就有问题 “1. 该代码表示「当页面初始化后」,所以这里不能依赖 log 、handleKeyDown 、” 可以看下 dan 的文章 https://overreacted.io/a-complete-guide-to-useeffect/ @ltaoo1o |
![]() | 29 ltaoo1o 279 天前 @LOWINC 我理解你的意思,useEffecet(fn, []) 不能表达「当页面初始化后」,和 componentDidMount 不同,这也是我无语的一点,函数组件没有一个明确的函数、方式来表达「当页面、组件初始化后」。 现在社区普遍都将 useEffecet(fn, []) 作为「当页面初始化」的含义来用不是吗,如果不这么写,可以给我一个方案吗,我确实不懂该如何写 |
![]() | 30 dango33 278 天前 @ltaoo1o #4 https://zh-hans.react.dev/learn/separating-events-from-effects 建议重新看一遍 React 的教程,把有些重要问题解释得很清楚了。 |
![]() | 31 ltaoo1o 278 天前 @dango33 完整看完了,这个教程不是证明了我说的吗,后面提到了 useEffectEvent ,一个「还没有发布的实验性 API 」,如果我用 useEffectEvent 包 log 就能解决我的问题,为什么要发布一个新的 hook ,就是有一些场景用 useEffect 解决不了。 另外,如果希望讨论,可以把你的观点明确地表达出来,我承认自己很菜,也欢迎讨论。 |
![]() | 32 dango33 278 天前 @ltaoo1o #31 “建议你读教程”并不是在尝试讽刺你菜,闻道有先后,不是说只有菜鸟才看教程。如果还没有读过,建议快速过一遍,尤其是对于从 class 时代过来的人。主要是能够(在一定程度上)避免踩坑,不至于对你提出的这个问题感到恼火(因为这并不是现阶段很难发现和解决的问题)。 如果对#15 的方式不满意,也可以选择把你原来的 log 函数扔给 ref ,这样更新 ref 的 effect 就只有一个依赖了。 或者用组件外的一个实例记录要上传的数据,提供暂存和上传数据的方法给组件用,这样 useEffect 就完全没有依赖了。(一点拙见) |
![]() | 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 (); } ``` 这样写就不用考虑框架的坑了,有问题也是我自己的问题了。 |
34 zhengfan2016 278 天前 第一次见到 react.fc 外面使用 settimeout ,黑马程序员也不是这么教的吧,这么写肯定容易出问题。就和不用 const 偏要用 var 一样 |
![]() | 36 ooo4 OP @zhengfan2016 因为我在调试 react 源码,不想通过合成事件去触发,徒增额外调试 |
![]() | 37 demonzoo 278 天前 |
![]() | 38 ltaoo1o 278 天前 @demonzoo 哥,感谢你,可是不是我想要的 我在注释里写了不能依赖 handleKeydown 等,不然语义就变了,变成了「当 handleKeydown 」改变时,监听 keydown 事件。但是我希望表达的是「当页面加载后,监听事件」 |
39 ljpCN 277 天前 @ltaoo1o 不知道你是没看到还是无视了我上面发的消息,既然你还是认为没有解决方案,我只能把解决方案写好发出来了: https://codesandbox.io/p/sandbox/zuo-yong-yu-de-wen-ti-forked-rqm5jy |
40 ljpCN 277 天前 @ltaoo1o 另外如果你真的接受了 react 文档中关于 useEffect 的定义,你应该知道 useEffect 的语义并非是 xx 发生变化时执行 yy 。useEffect 只是一个渲染过程的副作用,在严格模式下即使组件只挂载一次它也会执行两次。你不能把 useEffect 当做监听状态变化的回调来使用,而是应当作为每次渲染完成后的副作用来理解,只要你在 useEffect 的返回函数里对副作用做好适当的清理,你是不用去关心它执行了一次还是两次还是每次渲染后都执行的。当然你也可以用 useMemoizedFn 这样的 hook 来减轻你理解函数当前使用的是哪一次渲染的闭包变量的心智负担,这也完全没问题。 |
![]() | 42 ltaoo1o 277 天前 @ljpCN #40 确实是我太菜了,我很多框架、语言都写,无论 vue 、flutter 还是 swift 这些,都是有明确的 组件加载后 的语义的,我也按这个习惯来写,导致对 react 这里不熟悉,抱歉抱歉,忽略了你的消息。 |
![]() | 43 demonzoo 277 天前 @ltaoo1o 哥,感谢你,可是不是我想要的 我在注释里写了不能依赖 handleKeydown 等,不然语义就变了,变成了「当 handleKeydown 」改变时,监听 keydown 事件。但是我希望表达的是「当页面加载后,监听事件」 ================ 听哥们一句劝,其实我觉得其他人提到的在 input 里加 onKeyDown handler 是最好的方法,但你既然要用 useEffect 那我就帮你写了一个用 useEffect 实现的方法。 40 楼的哥们说的对,你对 useEffect 的理解恐怕有点偏颇,而且我觉得你对语义的追求也有些极端,这跟 “茴字的几种写法” 有什么区别呢 最后,如果你真的采用了 useMemoizedFn 这种解法,那我觉得真的是高射炮打蚊子了,本来几行的代码非要写成一百行,应用了一堆 useRef ,useMemo ,useEffect 等 hook ,本末倒置。。。 |
![]() | 44 ltaoo1o 277 天前 @demonzoo #43 是的我承认我对 useEffect 的理解不对,语义这个算个人追求,它能让我在不同框架、语言,用一套思维去写前端,也能让代码有可迁移、可维护、更好理解,当然这也是个人追求,不这样做完全是可以的,尤其是公司项目,能写完就行。 最后忘了怎么做的,是公司项目,代码比较复杂。我自己项目是抛弃了 hook ,只把 react 当视图渲染用,没有这些问题。 |
45 geekris1 277 天前 @ltaoo1o #4 一进页面就挂了 addEventListener 这时候对应函数里已经形成闭包了 没次取的 state 都是绑定那一刻 state 的值。 解决方法就是用 useRef, 写个新的 useEffect 监听 state 的变化,每次 state 更新同步更新 ref 的值 addEventListener 的函数里取 ref.current 这样就能保证每次取的都是最新的 上述操作也有对应的 hooks 工具可以参考 具体参考: https://ahooks.js.org/hooks/use-latest |
47 realJamespond 277 天前 useEffect 就是变化,return 就是变化前的回调,类似还有卸载前的回调 |