大概的代码如下:
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 的警告?
谢谢!
![]() | 1 noe132 2022-06-01 09:53:36 +08:00 这是 parent 的问题 so |
![]() | 2 noe132 2022-06-01 09:57:48 +08:00 还有就是不把 onChange 加到 deps 。说明你的 lint 规则太严格了。 另外还可以不要用 useEffect 来检测变化,直接让 onChange 冒泡,在每次 setCheckedItems 时手动调用 onChange |
3 xiaojun1994 2022-06-01 09:59:10 +08:00 建议看看 ahooks 的 useMemoizedFn ,或者 useLatest ,或者自己用 ref 把 onChange 存一下 |
![]() | 4 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 变了到底是真的逻辑变了,还是其实不想变的,所以这么做是不安全的 |
5 Leviathann 2022-06-01 10:18:42 +08:00 就是 useCallback 啊 react 是通过浅比较判断是否要走优化的 render path 你从 useState 里拿到的 setXXX 不会变实际上也就是相当于它帮你 useCallback 了 |
6 fengfuliu 2022-06-01 10:40:28 +08:00 用 useCallback 包没问题的 一般来说子组件的 props 如果是引用类型,都需要子组件 memo+父组件 useCallback |
7 fengfuliu 2022-06-01 10:42:32 +08:00 不过首先应该考虑不要用 useEffect 来检测变化,直接让 onChange 冒泡 |
![]() | 8 zhouyg 2022-06-01 10:57:40 +08:00 你本意是想 checkedItems 变化的时候通知外面,这样的写法是有点接近 vue 的 watch 思想,但在 react 里,你并不依赖 onChange ,应该封装一下 setCheckedItems ,在 set 的时候同时 onChange 到外部 |
9 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 |
![]() | 10 seki 2022-06-01 11:16:09 +08:00 两个问题 本来就是 parent 要负责传入的函数 ref 一致的,这个 design 是对的 这个 checkItems 状态是不必要的,如果把这个组件当成 controlled 的,数据流上会更清晰 |
![]() | 11 shenyu1996 2022-06-01 12:02:10 +08:00 onChange 可以去掉 onchange 变化组件也会刷新,useEffect 内也可以保证是最新的 onchange 当然主动触发 onchange 更符合直觉 |
12 zhuweiyou 2022-06-01 12:11:26 +08:00 封装一下 setCheckedItems , 改变 items 的同时 也调用 onChange, 不使用 useEffect. 要么就是 useCallback 了 |
![]() | 13 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 |
![]() | 14 yazoox OP |
16 shenjo 2022-06-02 15:55:45 +08:00 这样写逻辑都有点不对吧,onChange 变化就执行 onChange 函数? 你本意应该是 checkedItems 发生变化才会回调 onChange 。如果 onChange 里会做一些副作用操作不久 gg 了.比如 <MyComponent OnChange={()=> window.count++}/>。当然我举的例子不太合适。所以我觉得应该像其他楼里兄弟说的,考虑在 setCheckedItem 的同时去触发 onChange ,这才符合你想要表达的意思。 |
17 AyaseEri 2022-06-02 17:53:17 +08:00 实践的角度上,你应该无视 lint 的报错,将 onChange 从 dep list 里去掉。 |