一个关于 react 函数组件重新渲染的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
wiluxy
V2EX    React

一个关于 react 函数组件重新渲染的问题

  •  1
     
  •   wiluxy 2021-04-19 21:11:43 +08:00 3902 次点击
    这是一个创建于 1637 天前的主题,其中的信息可能已经有所发展或是发生改变。

    点击按钮后,触发 setCount(0+1), 此时 count 改变,改变后 count 为 1,函数重新渲染 输出"render" 然后再次点击按钮,触发 count+1,但是外面包了一层 useCallback,函数里面的 count 应该是第一次渲染时的 count,也就是 setCount(0+1),第二次点击 connt 是没有变的,但是函数还是重新渲染了,输出了"render",这是为什么呢?

    代码如下

    import React, { useCallback, useState } from "react"; const App = () => { console.log("render"); const [count, setCount] = useState(0); const Add = useCallback(() => { setCount(count + 1); }, []); return ( <div> {count} <button OnClick={Add}>add count</button> </div> ); }; export default App; 
    第 1 条附言    2021-04-20 10:06:34 +08:00
    感觉我的表达能力还是有点问题,有一个情况忘了说,就是第三次点击 add count 后(此时已经输出两次"render"),之后再点击多少次都不会再输出(输出 render 我理解为 count 更新->函数组件重新运行)
    25 条回复    2021-04-21 10:32:14 +08:00
    ericls
        2
    ericls  
       2021-04-19 21:20:49 +08:00 via iPhone
    不管 count 变没变 你 dispatch 了就是 dispatch 了
    huijiewei
        3
    huijiewei  
       2021-04-19 21:28:59 +08:00
    函数组件就是这样的,里面任何状态的变化都会重新运行函数 :)
    wiluxy
        4
    wiluxy  
    OP
       2021-04-19 21:32:01 +08:00
    djyde
        5
    djyde  
       2021-04-19 21:43:29 +08:00
    还有另外一个问题是 setCount(count + 1); 在 callback 里总是初始的 count, 应该用 setCount(count => count + 1)
    wiluxy
        6
    wiluxy  
    OP
       2021-04-19 22:11:46 +08:00
    @djyde 这里是为了让每次点击的时候 setCount 的值是一样才这么做的,setState 的时候会用 Object.is 比较新旧值,一样的话好像就不重新渲染页面了
    aaronlam
        7
    aaronlam  
       2021-04-19 22:54:54 +08:00
    只要调用了 useState 所返回数组里的第二个函数元素,就会重新执行函数组件
    wiluxy
        8
    wiluxy  
    OP
       2021-04-19 23:04:54 +08:00
    @aaronlam 这个例子里面只有前两次点击 add count 会输出“render”,后面再怎么点也不会输出 render 了,count 的值也没有变化
    zhuangzhuang1988
        9
    zhuangzhuang1988  
       2021-04-19 23:33:36 +08:00
    所以 react hook 反人类。。。
    imjamespond2020
        10
    imjamespond2020  
       2021-04-19 23:50:27 +08:00 via Android
    usecallback 的用法是传递到子组件的配合 useeffect 用的。。
    weimo383
        11
    weimo383  
       2021-04-19 23:50:38 +08:00 via Android
    你的 add 函数是一个在 mount 阶段就被缓存的函数,不会重新创建,生成闭包
    weimo383
        12
    weimo383  
       2021-04-19 23:53:27 +08:00 via Android   1
    函数执行并不代表组件的重新渲染。实际上 react 组件返回的是新的虚拟 dom
    shzx1994529
        13
    shzx1994529  
       2021-04-20 01:06:08 +08:00
    函数执行不等于组件渲染,后面可能会被 diff 掉,不用太在意
    ericgui
        14
    ericgui  
       2021-04-20 09:25:26 +08:00
    setCount(count => count + 1);
    dany813
        15
    dany813  
       2021-04-20 10:38:00 +08:00
    @shzx1994529 diff 多了 也挺费劲的
    shzx1994529
        16
    shzx1994529  
       2021-04-20 11:45:45 +08:00
    @dany813 也是,不过正常开发不乱写一般没啥性能问题
    wiluxy
        17
    wiluxy  
    OP
       2021-04-20 11:47:58 +08:00
    @ericgui 并不是要 setCount 能更新,而是 setCount 相同的值能触发函数更新 ,count 为 1 的时候 setCount(1),能触发更重新运行函数组件,但是后续触发又不触发了
    liuqiongyu889
        18
    liuqiongyu889  
       2021-04-20 13:37:39 +08:00
    你的依赖有问题,应该写为:
    cont Add = useCallback(() => {
    setCount(count + 1);
    }, [count]);

    不然 count 没有更新,还是原来的引用,可以看下这篇文章,解释 useCallback 和 useMemo 作用,为什么需要这个东东:

    [一句话解释 useCallback 与 useMemo 的区别 & 作用]( https://markdowner.net/article/153901518641561600)
    liuqiongyu889
        19
    liuqiongyu889  
       2021-04-20 13:39:58 +08:00
    如果你不想在依赖列表中写 count,应该参考 @ericgui 的写法,传递一个函数进去:
    setCount(count => count + 1);
    wiluxy
        20
    wiluxy  
    OP
       2021-04-20 13:43:51 +08:00
    @liuqiongyu889 这里是为了复现这个问题才这样写的,故意让 count 是第一次运行时的值( 0 ),第一次点击的时候 count 0->1,第二次点击的时候 count 1->1,理应是不会触发 App 函数重新运行的,结果触发了,但是第三次点击的时候 count 1->1,但是又没有触发运行,疑问点在这,不是 useCallback 的疑问,而是 useState,更新状态函数的疑问。
    liuqiongyu889
        21
    liuqiongyu889  
       2021-04-20 13:51:06 +08:00   1
    @wiluxy 这个我有点不解了,因为你这种做法有点违背 hook 的最佳实践用法,可能得挖挖源码看下喽,React Hook 最佳实践: https://markdowner.net/article/170279075167232000,外国人写的,翻译文。
    SystemLight
        22
    SystemLight  
       2021-04-20 17:19:04 +08:00
    我理解的是把函数式组件看成类组件的 render 方法,里面的 hook 相比类组件的其它定义方法,一旦组件内状态改变就会调用 render 方法去重新渲染生成新的 VDOM,而且是从根节点往下传递的
    wiluxy
        23
    wiluxy  
    OP
       2021-04-20 17:44:57 +08:00
    @SystemLight 已知的是 useState 的修改函数,传入的值新旧比较是用 Object.is 来比较的,如果一样的就不会进行更新

    ~~~js
    const [user,setUser] = useState({
    name:"tim"
    })

    setUser(u=>{
    u.name = "jojo"
    return u
    });
    ~~~
    向上面这样调用 setState 函数是不会触发更新的,但是我的疑问是 帖子内容的代码第一次执行 setCount(1)的时候函数重新渲染了,但是第二次 setCount(1),count 的值没有变化,函数还是重新渲染了,第三次第四五次之后再点又没有出现组件函数重新渲染的行为
    SystemLight
        25
    SystemLight  
       2021-04-21 10:32:14 +08:00   1
    粗略看了下 react-dom 源码,其中有一部分是这样子的

    ```
    function dispatchAction(fiber, queue, action) {
    ...

    if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
    ...
    if (objectIs(eagerState, currentState)) {
    // Fast path. We can bail out without scheduling React to re-render.
    // It's still possible that we'll need to rebase this update later,
    // if the component re-renders for a different reason and by that
    // time the reducer has changed.
    return;
    }
    }
    ...
    }
    ```

    状态改变后,第二次设置的值的时候,fiber.lanes 的标记会从 NoLanes 变为存在 SyncLane,如果没有同步的话是根本不会进行 objectIs 进行状态比较的,我的理解是 fiber 需要一回做同步,因为无法准确确定出某些事情,所以只能在下回合更新时进行执行判断
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5834 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 39ms UTC 01:51 PVG 09:51 LAX 18:51 JFK 21:51
    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