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

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

  •  
  •   yazoox · 301 天前 · 1907 次点击
    这是一个创建于 301 天前的主题,其中的信息可能已经有所发展或是发生改变。

    大概的代码如下:

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

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

    这个 checkItems 状态是不必要的,如果把这个组件当成 controlled 的,数据流上会更清晰
    shenyu1996
        11
    shenyu1996  
       301 天前
    onChange 可以去掉
    onchange 变化组件也会刷新,useEffect 内也可以保证是最新的 onchange
    当然主动触发 onchange 更符合直觉
    zhuweiyou
        12
    zhuweiyou  
       301 天前
    封装一下 setCheckedItems , 改变 items 的同时 也调用 onChange, 不使用 useEffect.
    要么就是 useCallback 了
    lujjjh
        13
    lujjjh  
       301 天前   ❤️ 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
       301 天前
    @lujjjh 哎,这个还没有发布呢。而且要 React18+才能够支持。
    我们的 enzyme 写的大量 unit test 还没有 migrate 到 testing-library 呢,估计还要好久才能够升级到 React18

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