请教一个关于 useEffect 依赖的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ooo4
V2EX    React

请教一个关于 useEffect 依赖的问题

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

    请教一个关于 useEffect 依赖的问题

    最近在学习 react 和 nextjs ,算初学者,感觉我写的很多 useEffect eslint 都提示缺少依赖,但其实我觉得写的依赖已经够了

    比如这样

     const [conversation, setConversation] = useState<Conversation[]>([]) useEffect(() => { if (currentChatTitle) { setConversation( conversation.map((i) => { return { ...i, title: i.id === currentChatId ? currentChatTitle : i.title, } }) ) } }, [currentChatId, currentChatTitle]) 

    eslint 就说缺少conversation这个依赖,但是加了之后就无限执行这个 useEffect 回调了,其实我连这个currentChatId都不想加入依赖

    eslint 也给了解决方案就是改成setXXX((prev)=>xxx),但这样好麻烦啊,或者就是 disable 掉这一行

     useEffect(() => { if (currentChatTitle) { // 改成`setXXX((prev)=>xxx)` setConversation((conversation) => conversation.map((i) => { return { ...i, title: i.id === currentChatId ? currentChatTitle : i.title, } }) ) } }, [currentChatId, currentChatTitle]) 

    请问下各位平时会关掉这个eslintreact-hooks/exhaustive-deps这个规则吗

    25 条回复    2025-02-26 00:58:04 +08:00
    darkengine
        1
    darkengine  
       233 天前
    能说说你这个代码要实现什么效果么,感觉逻辑不太对,可能要多引入一个 state
    MossFox
        2
    MossFox  
       233 天前   2
    就得用后面这段方案。否则的话,前面那个例子能跑通的原因也只是碰巧了,在于 每次因为其他 state 的变化导致的组件 rerender ,顺便更新了你的这个 useEffect 中的 conversation 。

    如果你用前面的方式发现某次显示的 conversation 是旧数据,那么就是掉坑里了。

    至于这个场景,我感觉可以试一下用 useMemo 。如果是要根据两个条件来筛选当前 active 的 conversation 或者类似的,那就
    const activeCOnversation= useMemo(() => {
    // 在这里面 map

    }, [conversation, xxx_id, xxx_title]);

    这样还能节约一次重绘,useEffect 那种 set 状态相当于 中括号里的变量变化一次重绘,useEffect 触发完因为 set 了 state 又触发一次重绘。useMemo 则可以是 一次绘制里面直接根据中括号里面的值,这一轮绘制就给变化的结果返回去。
    ooo4
        3
    ooo4  
    OP
       233 天前
    @darkengine 就是在/bar/foo 下的这个 page.tsx 更新了一个状态 currentChatTitle ,然后在/bar 下的 page.tsx 要触发 currentChatTitle 的副作用,再更新额外的状态
    ooo4
        4
    ooo4  
    OP
       233 天前
    @MossFox thanks ,我明天试试,顺便在看看文档
    wgbx
        5
    wgbx  
       232 天前
    react 的心智负担很大,往往对初学者不友好,首先,开发者理解的依赖和 react 需要的依赖是不一样的,你这个代码想在 currentChatId, currentChatTitle 变化时执行函数,但是每次函数更新时,conversation 因为在副作用引用了,也会更新,所以也需要监听,合理的办法确实是通过 setConversation 返回当前值进行更新

    如果你连 currentChatId 都不想加,说明 currentChatId 本身是常量或者不更新的值,你应该使用 useMemo 包裹起来,避免重复计算

    eslint 的规则不能关闭,他确实能反映依赖的问题,但是 ahooks 是必须使用的,作为 react 的 hooks 包装,能节省很多代码,另外你这个写法是不能够优化,currentChatTitle 在什么时候更新?初始化更新应该放在 useMount 上,事件触发应该放在函数里,这种情况的副作用不多见
    mizuki9
        6
    mizuki9  
       232 天前
    eslintreact-hooks/exhaustive-deps 规则应该设置报 warning ,不要报 error 。
    多个 state 有时候是可以合并成一个的,某些情况可以解决 useEffect 依赖报 warning 。
    明确知道自己逻辑正确的时候,忽略 warning 就好了,react 写多了就习惯了
    FlashEcho
        7
    FlashEcho  
       232 天前
    set 函数可以传入一个函数作为参数,也很清晰啊,这个更新函数接受旧值,返回新值,而且还更安全。如果 set 函数传入值,获取到外面的旧的 conversation 怎么办,传入函数就没问题了

    上面有人推荐使用 useMemo ,我不太建议,看起来你的这更新就是数组替换一下,这个渲染也不太复杂,没有必要缓存,缓存本身也是有成本的,无脑 useMemo 不是全是好处的,不考虑本身的成本,心智负担还变重了
    darkengine
        8
    darkengine  
       232 天前
    @MossFox 的方案是对的,就是要引入一个 activeConversation 用来渲染当前标题,不应该是去改 conversation (应该叫 conversations?)
    wiluxy
        9
    wiluxy  
       232 天前
    典型的滥用 useEffect 的例子,@MossFox 的方案是对的,至于 @chesha1 说的不推荐 useMemo ,这里 useMemo 的作用类似于派生状态,心智负担比用 useEffect 小
    ooo4
        10
    ooo4  
    OP
       232 天前
    @wgbx 不加这个 currentChatId 能满足我的功能,但 eslint 会抛出警告,加了又不满足我的功能了,react 太难了。
    wiluxy
        11
    wiluxy  
       232 天前
    @ooo4 如果不加这个 currentChatId 可以满足你的功能,非要用 useEffect 的话,可以考虑一下用 useReducer 组织状态

    ```typescript
    import React, {useEffect, useReducer, useState} from "react"


    type COnversation= {
    id:string
    title:string
    [key:string]:unknown
    }

    const initialState:Conversation[] = [
    {id:'1',title:"title-1"},
    {id:'2',title:"title-2"},
    {id:'3',title:"title-3"},
    ]

    function App() {
    const [currentChatId,setCurrentChatId] = useState("")
    const [currentChatTitle,setCurrentChatTitle] = useState("")

    const [conversation,setConversation] = useReducer<
    React.Reducer<
    Conversation[],
    (prev:Conversation[],curChatId:typeof currentChatId)=>Conversation[]
    >
    >((prev,action)=>{
    console.log(123)
    return action(prev,currentChatId)
    },initialState)

    useEffect(()=>{
    if(currentChatTitle){
    setConversation((prev,curChatId)=>{
    console.log({curChatId})
    return prev.map(i=>{
    return {
    ...i,
    title: i.id === curChatId ? currentChatTitle : i.title,
    }
    })
    })
    }
    },[currentChatTitle])

    return <div>
    <label htmlFor="currentChatId">currentChatId:</label>
    <input type="text" id="currentChatId" value={currentChatId} OnChange={(e)=>setCurrentChatId(e.target.value)} />
    <label htmlFor="currentChatTitle">currentChatTitle:</label>
    <input type="text" id="currentChatTitle" value={currentChatTitle} OnChange={(e)=>setCurrentChatTitle(e.target.value)} />
    <ul>
    {
    conversation.map(i=>{
    return <li key={i.id}>{i.title}</li>
    })
    }
    </ul>
    </div>
    }

    export default App;


    ```
    ooo4
        12
    ooo4  
    OP
       232 天前
    @wiluxy 感谢
    ooo4
        13
    ooo4  
    OP
       232 天前
    @MossFox 改成了 useMemo 解决我的问题了,逻辑也清晰多了
    ```js
    const cOnversationInfo= useMemo(() => {
    return conversation.map((i) => ({
    ...i,
    active: i.id === currentChatId,
    title:
    currentChatTitle && i.id === currentChatId ? currentChatTitle : i.title,
    }))
    }, [conversation, currentChatTitle, currentChatId])
    ```
    jchnxu
        14
    jchnxu  
       232 天前
    @MossFox 老哥和我想的一模一样。我直觉也是这两种解法。
    NerdHND
        15
    NerdHND  
       232 天前
    多用 useMemo, 如果实在是需要通过变更修改 state, 用 useReducer 也可以解决很多问题. 当然, 还是最好多构建单项数据流, 少用 flag
    yunyuyuan
        16
    yunyuyuan  
       232 天前
    2 楼说的很详细了。其实只要理解 react 的每一次渲染都是一个“单独”的闭包,里面的所有 state 都只代表当前渲染,把它当作一次性的,只用来计算和展示,就能搞明白大部分问题
    FlashEcho
        17
    FlashEcho  
       232 天前
    @wiluxy #9 确实,我都没想到直接不要 useEffect 这个问题,最好做法肯定还是直接在 currentChatId 和 currentChatTitle 变化的事件内部直接用 setConversation 。我不推荐 useMemo ,主要是 useMemo 用起来尤其麻烦,需要注意 Memo 的依赖从头到尾都没问题,不然如果传递的层数比较深,谁随手搞一下 props 缓存就失效了(还有一些别的 memo 失效的场景,以前看的文章有点忘记了),所以我的想法是 profiler 之后再优化比较好?不太懂 react ,不知道这个做法是否最优
    wiluxy
        18
    wiluxy  
       232 天前
    @chesha1 useMemo 、useEffect 漏依赖的问题,可以装个 eslint 插件(@eslint-react/eslint-plugin
    )辅助查看依赖有没有问题,遵循 hooks 规则写下去,等以后 react compiler 完善自动优化吧,现在没有性能问题就不用管太多了,只要功能正常、没有性能问题,re-render 不是什么大事情,老是想着最佳实践很累的。
    X_Del
        19
    X_Del  
       232 天前   3
    不一定要多用 useMemo ,但一定要少用 useEffect 。
    见到很多 React 新人 useEffect 的时候,会创建很多多余的 state ,比如下面这种代码:

    ```
    const [lightColor, setLightColor] = useState<'red' | 'yellow' | 'green'>('red');
    const [canPass, setCanPass] = useState<boolean>(false);

    useEffect(() => {
    if (lightColor === 'green') setCanPass(true);
    else setCanPass(false);
    }, [lightColor]);
    ```

    这里 canPass 不该是一个 state ,根本就是一个 computed value ,用 useMemo 才对:

    ```
    const [lightColor, setLightColor] = useState<'red' | 'yellow' | 'green'>('red');
    const canPass = useMemo(() => lightColor === 'green', [lightColor]);
    ```

    大多数场合 useMemo 也是多余的,遇到性能问题再优化就可以:

    ```
    canPass = lightColor === 'green';
    ```

    所以我给 React 新人的建议都是:少用 useEffect ,如果遇到了必须 useEffect 的 case ,看看 ahooks 等库里有没有现成的 hook 。
    importmeta
        20
    importmeta  
       231 天前
    这个问题是 eslint 搞得, 其实也可以不用管, 我调用第三方函数的时候, 这个依赖列表还是保留空数组, 它报警告就报警告, 等熟练了, 知道到底怎么依赖的时候, 关掉这条 eslint 规则就行.
    ZztGqk
        21
    ZztGqk  
       231 天前
    react 的去年由 Dan 更新的新 doc 很详细的说,https://react.dev/learn/you-might-not-need-an-effect ,很多时候你只是需要一个新的值,而不是修改一个已有的值,由于 function 会被执行多次,所以这个概念相对常见,甚至在某些轻度转换的情况无需 memo ,至于使用 memo 或者 callback 来存值的概念我觉得更像 vue3 或者 solid 那种只执行一次的概念,更像是 computed 。不使用 memo 直接 const 一个新的值可以配合 react compiler 来帮你完成这种包装。另外随着项目的增长你可以需要 Jotai 或者 别的状态库,我觉得从 SPA (直接使用 vite + react router (非 Remix 版本,这里有点混乱,去年 Remix 和 React Router 合并了,导致现在 v7 有些部分为了支持 Server Componet 变得不再纯粹,可以参考 v6 的文档逐步过渡)过渡到 SSR 学习曲线会稍微舒服一些,毕竟我觉得 next 里的 server Compoent 的概念会阻碍初学者对于状态管理的认知...
    ZztGqk
        22
    ZztGqk  
       231 天前
    @X_Del 是的这种情况我觉得直接 const 一个新值就可以了,现在有 React Compiler ,Memo 直接让它自动去加就行了。
    loveloki32
        23
    loveloki32  
       228 天前 via iPhone
    21 楼的说得很不错。

    不是所有的东西都需要做状态管理,如果数据量不大,你这个变量也不需要 memo ,直接 const 定义一个新变量即可。

    另外补充一些:


    1. 你可以一方面看官方文档,另外看下 Dan 的博客( https://overreacted.io ),里面讲了很多“react 思维方式”

    2. 对于初学者来说先不要不学 react19 的服务端组件,这个会麻烦一点(不过这个会逼着你重新思考状态管理,另一个角度是好事),你用 nextjs 可能会大量时间在这个上面踩坑,如果发现这个上面花费时间有点多的话,可以用 vite 创建项目去学习。

    3. 对于官方文档有问题也可以直接直接提 issue 或者 pr ,有的时候可能是文档翻译得不好导致你理解出了偏差(中文仓库: https://github.com/reactjs/zh-hans.react.dev
    loveloki32
        24
    loveloki32  
       228 天前 via iPhone
    另外不要忽略 eslint 的提示,一般来说都是代码逻辑有问题,可能需要重新思考下写法
    loveloki32
        25
    loveloki32  
       228 天前
    啊没发现打错字了。

    先不要学 react19 的服务端组件,这个心智负担太重了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1203 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 17:29 PVG 01:29 LAX 10:29 JFK 13:29
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86