V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yazoox
V2EX  ›  React

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

  •  
  •   yazoox · 2022-06-01 09:50:46 +08:00 · 2722 次点击
    这是一个创建于 940 天前的主题,其中的信息可能已经有所发展或是发生改变。

    大概的代码如下:

    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 的警告?

    谢谢!

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

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

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

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

    @zhouyg en. 这样更自然一点
    dany813
        15
    dany813  
       2022-06-02 13:04:53 +08:00
    @lujjjh 这个 useEvent 怎么看着像,useRef 的封装
    shenjo
        16
    shenjo  
       2022-06-02 15:55:45 +08:00
    这样写逻辑都有点不对吧,onChange 变化就执行 onChange 函数? 你本意应该是 checkedItems 发生变化才会回调 onChange 。如果 onChange 里会做一些副作用操作不久 gg 了.比如 <MyComponent onChange={()=> window.count++}/>。当然我举的例子不太合适。所以我觉得应该像其他楼里兄弟说的,考虑在 setCheckedItem 的同时去触发 onChange ,这才符合你想要表达的意思。
    AyaseEri
        17
    AyaseEri  
       2022-06-02 17:53:17 +08:00
    实践的角度上,你应该无视 lint 的报错,将 onChange 从 dep list 里去掉。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5476 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 08:57 · PVG 16:57 · LAX 00:57 · JFK 03:57
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.