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

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

  •  1
     
  •   wiluxy · 23 天前 · 1156 次点击

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

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

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

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

    setUser(u=>{
    u.name = "jojo"
    return u
    });
    ~~~
    向上面这样调用 setState 函数是不会触发更新的,但是我的疑问是 帖子内容的代码第一次执行 setCount(1)的时候函数重新渲染了,但是第二次 setCount(1),count 的值没有变化,函数还是重新渲染了,第三次第四五次之后再点又没有出现组件函数重新渲染的行为
    7anshuai
        24
    7anshuai   22 天前
    SystemLight
        25
    SystemLight   21 天前   ❤️ 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 需要一回做同步,因为无法准确确定出某些事情,所以只能在下回合更新时进行执行判断
    关于   ·   帮助文档   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   945 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 18ms · UTC 21:24 · PVG 05:24 · LAX 14:24 · JFK 17:24
    ♥ Do have faith in what you're doing.