目录
一.思考
二. 举例
三.验证
四.小结
一.首先不太了解的先看官方文档 本文的目的,是想通过一个简单的例子详细分析一些令人疑惑的问题及其背后的原因。
思考: 先来看个例子
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
// console.log(count);
setCount(count + 1);
}, 1000);
}, []);
return <h1>{count}</h1>;
}
我们期望,useEffect只执行一次,且后续每隔1s,count自动+1,然而,实际上count从0到1后,再没有变化,一直都是1。
难道setInterval没有执行,于是我们带着疑惑加上了console.log
事实是,setInterval每次执行的时候,拿到的count都得0,很自然的我们会想到闭包,但是闭包能完全解释这个现象吗?
我们稍加修改再看下这个例子:
function Counter() {
const [count, setCount] = useState(0);
let num = 0;
useEffect(() => {
const id = setInterval(() => {
// 通过 num 来给 count 提供值
console.log(num);
setCount(++num);
}, 1000);
}, []);
return <h1>{count}</h1>;
}
我们可以看到,借助num 这个中间变量,我们可以得到想要的结果。但是,同样是闭包,为什么num就能记住之前的的值呢?
其实问题出在count上,继续往下看:
function Counter() {
// ...
console.log('我是 num', num);
return <h1>{count}-----{num}</h1>;
}
渲染的num和定时器中的 num 为什么不一样呢?
每次都是重新执行
到这里我想说的到底是什么呢?
我们可以清晰的看到渲染出的num和setinterval中的num,是不同的。
这是因为在React中,对于函数式组件来讲,每次更新都会重新执行一次函数。也就是说,每次更新都会在当前作用域重新声明一个 let num=0
,所以,定时器中闭包引用的那个num,和每次更新时渲染的num,根本不是同一个。当然,我们能很轻易地把他们变成同一个。
let num = 0; // 将声明放到渲染组件外面
function Counter() {
// ...
return <h1>{count}-----{num}</h1>;
}
嗯,说了这么多,跟count 有什么关系呢?
同理,正因为函数组件每次都会整体重新执行,那么HOOKS当然也是这样。
function Counter() {
const [count, setCount] = useState(0);
// ...
}
useState应该理解为和普通的javascript函数一样,而不是React的什么黑魔法函数组件更新的时候,useState会重新执行,对应的,也会重新声明[count, setCount]这一组常量。只不过React对这个函数做了一些特殊处理,比如:
首次执行时,会将useState的参数初始化给count,而以后在执行时,则会直接取上次setCount(如果有调用)赋过的值(React通过某种方式保存起来的)。
有了这个概念,就不难知道,定时器里的setCount(count + 1)
,这个 count 和每次更新重新声明的 count,也是完全不同的两个常量,只不过它们的值,可能会相等。
比如,我们尝试把之前的 num,直接用 count 替代。
function Counter() {
// 注意这里变成 let
let [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
// 这种写法是不好的
setCount(++count);
}, 1000);
}, []);
console.log(count);
return <h1>{count}</h1>;
}
这时候不论是打印还是页面表现都和你期望的一样。
但这违背了React的原则,而且也让程序变得更加让人迷惑。
也就导致了你并不能清楚的知道:此时渲染的count和set interval中的count已经不是同一个了。尽管他们的值是相等的。
当然,这种场景下react提供了可行的方法,能够每次拿到count的最新值,就是给setCount传递一个回调函数。
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
// 注意:这里变成回调了
setCount(count => count + 1);
}, 1000);
}, []);
return <h1>{count}</h1>;
}
执行图解
回过头再看看开始的例子:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
}, []);
return <h1>{count}</h1>;
}
小结:
count 每次都被重新声明了,setInterval 因为 useEffect 设置了只执行一次的缘故,在第一次更新时闭包引用的 count 始终是 0,后续更新的 count 和它没关系。