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

19 天前
 ooo4

版本: react@18.3.1

我看源码就是先 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)
2506 次点击
所在节点    React
47 条回复
ljpCN
19 天前
@ltaoo1o 那给你看看我改完的代码吧,个人觉得比你的语义更清晰。https://codesandbox.io/p/sandbox/zuo-yong-yu-de-wen-ti-forked-6ljjys
ltaoo1o
19 天前
@ljpCN #21 额,语义是指业务逻辑,就比如我必须表达出「当页面加载后,监听回车」,在这个前提下,如何完成需求,你的代码非常好,没有问题。只是少了「当页面加载后」的这个含义。
ljpCN
19 天前
@ltaoo1o 如果你是想要在整个页面监听回车按键,先不讨论这个需求的合理性,为了实现你要的语义清晰,你应该寻求对 hooks 的封装来实现你的 log 函数拿到最新的 state ,或者直接通过 ref 获取 input 标签当前的 value 。前者的话举一个例子: https://ahooks.js.org/hooks/use-memoized-fn
shintendo
19 天前
@ljpCN 你这个跟他的不一样,他监听了 document ,在输入框外面 enter 也能触发
shintendo
19 天前
@ltaoo1o 容易漏是指漏依赖? eslint 可以检查啊
ltaoo1o
19 天前
@shintendo #25 心智负担,而且代码会比较丑🤣
ljpCN
19 天前
@shintendo 嗯,我上面也说了要想全局监听的解决方案。另外我主要想表达的是 hooks 带来的心智负担不足以成为因噎废食的理由。 @ltaoo1o 这位老哥所说的“用函数组件就是写不出来”是不成立的;“hook 改变了代码作为领域知识的意义,现在都是在写框架代码,不是写业务代码”则缺少论证看起来是个人偏见。
LOWINC
18 天前
你这个前提就有问题
“1. 该代码表示「当页面初始化后」,所以这里不能依赖 log 、handleKeyDown 、”

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

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

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

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

这样写就不用考虑框架的坑了,有问题也是我自己的问题了。
zhengfan2016
18 天前
第一次见到 react.fc 外面使用 settimeout ,黑马程序员也不是这么教的吧,这么写肯定容易出问题。就和不用 const 偏要用 var 一样
dango33
18 天前
@ltaoo1o #33 函数组件整个一大闭包,所以这些“坑”对于我来说反而挺符合直觉的 XD 。
ooo4
18 天前
@zhengfan2016 因为我在调试 react 源码,不想通过合成事件去触发,徒增额外调试
demonzoo
18 天前
ltaoo1o
18 天前
@demonzoo 哥,感谢你,可是不是我想要的 🤣 我在注释里写了不能依赖 handleKeydown 等,不然语义就变了,变成了「当 handleKeydown 」改变时,监听 keydown 事件。但是我希望表达的是「当页面加载后,监听事件」
ljpCN
17 天前
@ltaoo1o 不知道你是没看到还是无视了我上面发的消息,既然你还是认为没有解决方案,我只能把解决方案写好发出来了: https://codesandbox.io/p/sandbox/zuo-yong-yu-de-wen-ti-forked-rqm5jy
ljpCN
17 天前
@ltaoo1o 另外如果你真的接受了 react 文档中关于 useEffect 的定义,你应该知道 useEffect 的语义并非是 xx 发生变化时执行 yy 。useEffect 只是一个渲染过程的副作用,在严格模式下即使组件只挂载一次它也会执行两次。你不能把 useEffect 当做监听状态变化的回调来使用,而是应当作为每次渲染完成后的副作用来理解,只要你在 useEffect 的返回函数里对副作用做好适当的清理,你是不用去关心它执行了一次还是两次还是每次渲染后都执行的。当然你也可以用 useMemoizedFn 这样的 hook 来减轻你理解函数当前使用的是哪一次渲染的闭包变量的心智负担,这也完全没问题。

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

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

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

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

© 2021 V2EX