点击按钮后,触发 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
mxT52CRuqR6o5 2021-04-19 21:17:35 +08:00 1
|
2
ericls 2021-04-19 21:20:49 +08:00 via iPhone
不管 count 变没变 你 dispatch 了就是 dispatch 了
|
3
huijiewei 2021-04-19 21:28:59 +08:00
函数组件就是这样的,里面任何状态的变化都会重新运行函数 :)
|
4
wiluxy OP @mxT52CRuqR6o5 感谢
|
5
djyde 2021-04-19 21:43:29 +08:00
还有另外一个问题是 setCount(count + 1); 在 callback 里总是初始的 count, 应该用 setCount(count => count + 1)
|
6
wiluxy OP |
7
aaronlam 2021-04-19 22:54:54 +08:00
只要调用了 useState 所返回数组里的第二个函数元素,就会重新执行函数组件
|
8
wiluxy OP @aaronlam 这个例子里面只有前两次点击 add count 会输出“render”,后面再怎么点也不会输出 render 了,count 的值也没有变化
|
9
zhuangzhuang1988 2021-04-19 23:33:36 +08:00
所以 react hook 反人类。。。
|
10
imjamespond2020 2021-04-19 23:50:27 +08:00 via Android
usecallback 的用法是传递到子组件的配合 useeffect 用的。。
|
11
weimo383 2021-04-19 23:50:38 +08:00 via Android
你的 add 函数是一个在 mount 阶段就被缓存的函数,不会重新创建,生成闭包
|
12
weimo383 2021-04-19 23:53:27 +08:00 via Android 1
函数执行并不代表组件的重新渲染。实际上 react 组件返回的是新的虚拟 dom
|
13
shzx1994529 2021-04-20 01:06:08 +08:00
函数执行不等于组件渲染,后面可能会被 diff 掉,不用太在意
|
14
ericgui 2021-04-20 09:25:26 +08:00
setCount(count => count + 1);
|
15
dany813 2021-04-20 10:38:00 +08:00
@shzx1994529 diff 多了 也挺费劲的
|
16
shzx1994529 2021-04-20 11:45:45 +08:00
@dany813 也是,不过正常开发不乱写一般没啥性能问题
|
17
wiluxy OP @ericgui 并不是要 setCount 能更新,而是 setCount 相同的值能触发函数更新 ,count 为 1 的时候 setCount(1),能触发更重新运行函数组件,但是后续触发又不触发了
|
18
liuqiongyu889 2021-04-20 13:37:39 +08:00
你的依赖有问题,应该写为:
const Add = useCallback(() => { setCount(count + 1); }, [count]); 不然 count 没有更新,还是原来的引用,可以看下这篇文章,解释 useCallback 和 useMemo 作用,为什么需要这个东东: [一句话解释 useCallback 与 useMemo 的区别 & 作用]( https://markdowner.net/article/153901518641561600) |
19
liuqiongyu889 2021-04-20 13:39:58 +08:00
如果你不想在依赖列表中写 count,应该参考 @ericgui 的写法,传递一个函数进去:
setCount(count => count + 1); |
20
wiluxy OP @liuqiongyu889 这里是为了复现这个问题才这样写的,故意让 count 是第一次运行时的值( 0 ),第一次点击的时候 count 0->1,第二次点击的时候 count 1->1,理应是不会触发 App 函数重新运行的,结果触发了,但是第三次点击的时候 count 1->1,但是又没有触发运行,疑问点在这,不是 useCallback 的疑问,而是 useState,更新状态函数的疑问。
|
21
liuqiongyu889 2021-04-20 13:51:06 +08:00 1
@wiluxy 这个我有点不解了,因为你这种做法有点违背 hook 的最佳实践用法,可能得挖挖源码看下喽,React Hook 最佳实践: https://markdowner.net/article/170279075167232000,外国人写的,翻译文。
|
22
SystemLight 2021-04-20 17:19:04 +08:00
我理解的是把函数式组件看成类组件的 render 方法,里面的 hook 相比类组件的其它定义方法,一旦组件内状态改变就会调用 render 方法去重新渲染生成新的 VDOM,而且是从根节点往下传递的
|
23
wiluxy OP @SystemLight 已知的是 useState 的修改函数,传入的值新旧比较是用 Object.is 来比较的,如果一样的就不会进行更新
~~~js const [user,setUser] = useState({ name:"tim" }) setUser(u=>{ u.name = "jojo" return u }); ~~~ 向上面这样调用 setState 函数是不会触发更新的,但是我的疑问是 帖子内容的代码第一次执行 setCount(1)的时候函数重新渲染了,但是第二次 setCount(1),count 的值没有变化,函数还是重新渲染了,第三次第四五次之后再点又没有出现组件函数重新渲染的行为 |
24
7anshuai 2021-04-20 20:35:59 +08:00
|
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 需要一回做同步,因为无法准确确定出某些事情,所以只能在下回合更新时进行执行判断 |