有关 react.useEffect dependency list 的一个问题,如何避免频繁触发?

2022-06-01 09:50:46 +08:00
 yazoox

大概的代码如下:

interface Props {
  onChange?: () => void;
}

const MyComponent: React.FC<Props> = (props: Props) => {
  const { onChange } = props;
  const [checkedItems, setCheckedItems] = React.useState([]);
  
  React.useEffect(() => {
    onChange?.(checkedItems);
  }, [checkedItems, onChange]);
}

能够正常工作,但是有一个问题,该 effect 每次 re-render 的时候,都会被调用。
因为 parent component 也是一个 function component ,所以,onChange 的 reference/pointer 总是在变。
我想把 onChange 从 dependency list 里面去掉,但 ts/eslint 一直警告,不让我这么干...

我现在能够做的就是在 parent component 里面,使用 useCallback 或者 useMemo 记住这个方法,再传递给子组件 MyComponent's props

const onChange = useCallback(()=> {});

虽然能够解决问题,但要求 parent 在使用 MyComponent 的时候,知道这个细节。我觉得这样不是好的 design 。

有没有什么办法改进一?难道要去 disable ts/eslint 的警告?

谢谢!

2714 次点击
所在节点    React
17 条回复
noe132
2022-06-01 09:53:36 +08:00
这是 parent 的问题 so
noe132
2022-06-01 09:57:48 +08:00
还有就是不把 onChange 加到 deps 。说明你的 lint 规则太严格了。
另外还可以不要用 useEffect 来检测变化,直接让 onChange 冒泡,在每次 setCheckedItems 时手动调用 onChange
xiaojun1994
2022-06-01 09:59:10 +08:00
建议看看 ahooks 的 useMemoizedFn ,或者 useLatest ,或者自己用 ref 把 onChange 存一下
otakustay
2022-06-01 10:04:40 +08:00
最正规的是别这么玩,你在哪 setState 就在哪调用 onChange
如果硬要这么玩,就拿 useRef 包一下 onChange ,可以参考这个: https://github.com/ecomfe/react-hooks/blob/master/packages/intended-lazy/src/index.ts
但其实你是不知道外面 onChange 变了到底是真的逻辑变了,还是其实不想变的,所以这么做是不安全的
Leviathann
2022-06-01 10:18:42 +08:00
就是 useCallback 啊
react 是通过浅比较判断是否要走优化的 render path
你从 useState 里拿到的 setXXX 不会变实际上也就是相当于它帮你 useCallback 了
fengfuliu
2022-06-01 10:40:28 +08:00
用 useCallback 包没问题的 一般来说子组件的 props 如果是引用类型,都需要子组件 memo+父组件 useCallback
fengfuliu
2022-06-01 10:42:32 +08:00
不过首先应该考虑不要用 useEffect 来检测变化,直接让 onChange 冒泡
zhouyg
2022-06-01 10:57:40 +08:00
你本意是想 checkedItems 变化的时候通知外面,这样的写法是有点接近 vue 的 watch 思想,但在 react 里,你并不依赖 onChange ,应该封装一下 setCheckedItems ,在 set 的时候同时 onChange 到外部
CodingNaux
2022-06-01 11:12:17 +08:00
1. 为什么要用 useEffect 去触发 onChange,什么地方 setCheckedItems ,什么地方调 onChange 不行吗
2. useMemoizedFn 或者 usePersistFn ,直接学习他们源代码,体会下为什么这么写
https://github.com/alibaba/hooks/blob/master/packages/hooks/src/useMemoizedFn/index.ts
https://github.com/alibaba/hooks/blob/v2.10.14/packages/hooks/src/usePersistFn/index.ts
seki
2022-06-01 11:16:09 +08:00
两个问题

本来就是 parent 要负责传入的函数 ref 一致的,这个 design 是对的

这个 checkItems 状态是不必要的,如果把这个组件当成 controlled 的,数据流上会更清晰
shenyu1996
2022-06-01 12:02:10 +08:00
onChange 可以去掉
onchange 变化组件也会刷新,useEffect 内也可以保证是最新的 onchange
当然主动触发 onchange 更符合直觉
zhuweiyou
2022-06-01 12:11:26 +08:00
封装一下 setCheckedItems , 改变 items 的同时 也调用 onChange, 不使用 useEffect.
要么就是 useCallback 了
lujjjh
2022-06-01 12:36:50 +08:00
有人提到最佳实践,最佳实践不是一成不变的,有时候 React 里的最佳实践只是因为目前只能这么做。

可以关注下 useEvent 这个 RFC ,链接指向的部分就是在描述类似的问题: https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md#useeffect-shouldnt-re-fire-when-event-handlers-change
yazoox
2022-06-01 16:00:24 +08:00
@lujjjh 哎,这个还没有发布呢。而且要 React18+才能够支持。
我们的 enzyme 写的大量 unit test 还没有 migrate 到 testing-library 呢,估计还要好久才能够升级到 React18

@zhouyg en. 这样更自然一点
dany813
2022-06-02 13:04:53 +08:00
@lujjjh 这个 useEvent 怎么看着像,useRef 的封装
shenjo
2022-06-02 15:55:45 +08:00
这样写逻辑都有点不对吧,onChange 变化就执行 onChange 函数? 你本意应该是 checkedItems 发生变化才会回调 onChange 。如果 onChange 里会做一些副作用操作不久 gg 了.比如 <MyComponent onChange={()=> window.count++}/>。当然我举的例子不太合适。所以我觉得应该像其他楼里兄弟说的,考虑在 setCheckedItem 的同时去触发 onChange ,这才符合你想要表达的意思。
AyaseEri
2022-06-02 17:53:17 +08:00
实践的角度上,你应该无视 lint 的报错,将 onChange 从 dep list 里去掉。

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

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

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

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

© 2021 V2EX