React 中的 setInterval - 意外行为
setInterval in React - unexpected behavior
我用react写了一个简单的秒表。
为了每秒呈现新数字,我使用了 setInterval:
import React, {useEffect, useState, useRef} from 'react';
const MAX_TIMER = 5;
export default function StopWatch() {
const [timer, setTimer] = useState(0);
const tickIntervalId = useRef(null);
const tickTimer = (getTimerTimeFn) => {
const number = getTimerTimeFn();
setTimer(number);
if(number === MAX_TIMER) {
clearInterval(tickIntervalId.current);
}
}
let i = 0;
console.log('i is: ', i) // i value is 0 every render
const incrementNumber = () => {
i += 1;
console.log('in incrementNumber, i is: ', i); // i value is incrementing every render: 1, 2, 3, 4, 5. how?
return i;
}
useEffect(() => {
tickIntervalId.current = setInterval(tickTimer, 1000, incrementNumber);
}, [])
return (
<div>
{timer}
</div>
);
}
问题是 - 为什么这个秒表会按预期工作?
因为每一秒我都会重新渲染组件,let i = 0
行会再次执行,所以我希望
incrementNumber
的返回值将始终为 1 (0 + 1)
但相反,返回值递增:1、2、3、4、5。
这是控制台输出:
i is: 0
in incrementNumber, i is: 1
i is: 0
in incrementNumber, i is: 2
i is: 0
in incrementNumber, i is: 3
i is: 0
in incrementNumber, i is: 4
i is: 0
in incrementNumber, i is: 5
i is: 0
您需要了解词法环境和执行上下文以及范围如何工作
这里已经讨论过了
几乎相似的例子
我认为这是一个范围问题,如果你在 useEffect 中给出 console.log (i),我们可以看到它只在第一次渲染时记录。好像 useEffect 中的 let i 与组件的不同。即使使用新的渲染,useEffect 中的状态仍然存在。
useEffect(() => {
console.log("i in effect: ", i)
tickIntervalId.current = setInterval(tickTimer, 1000, incrementNumber);
}, []);
下面是一个演示词法范围问题的小例子。
function myFn() {
let i = 0; // `i` is local to myFn() (has local scope)
console.log(`myFn i == ${i}`);
return (caller) => {
i++;
console.log(`${caller} : i == ${i}`);
}
}
let a = myFn(); // 'myFn i == 0'
a('a'); // 'a : i == 1'
let b = myFn(); // 'myFn i == 0'
b('b'); // 'b : i == 1'
b('b'); // 'b : i == 2'
b('b'); // 'b : i == 3'
a('a'); // 'a : i == 2'
@Ido
这里 link 更有意义:我正在使用一个对象,因此您可以看到变量的存储位置。我也赞成所有其他答案,因为它们是正确的。这是范围问题,而不是 React 问题。
https://codesandbox.io/s/material-demo-wj0pm
import React, { useEffect, useState, useRef } from "react";
const MAX_TIMER = 5;
let renderCount = 0;
export default function StopWatch() {
const [timer, setTimer] = useState({});
const tickIntervalId = useRef(null);
const tickTimer = getTimerTimeFn => {
const number = getTimerTimeFn();
setTimer({...number});
if (number === MAX_TIMER) {
clearInterval(tickIntervalId.current);
}
};
var iObject = {
i: 0,
inRender: renderCount,
};
renderCount++;
console.log("i is: ", JSON.stringify(iObject) ); // i value is 0 every render
const incrementNumber = () => {
iObject.i += 1;
console.log("in incrementNumber, i is: ", JSON.stringify(iObject)); // i value is incrementing every render: 1, 2, 3, 4, 5. how?
console.log("------------------------");
return iObject; //Not a copy
};
useEffect(() => {
tickIntervalId.current = setInterval(tickTimer, 1000, incrementNumber);
}, []);
return <div>{JSON.stringify(timer)}</div>;
}
我用react写了一个简单的秒表。
为了每秒呈现新数字,我使用了 setInterval:
import React, {useEffect, useState, useRef} from 'react';
const MAX_TIMER = 5;
export default function StopWatch() {
const [timer, setTimer] = useState(0);
const tickIntervalId = useRef(null);
const tickTimer = (getTimerTimeFn) => {
const number = getTimerTimeFn();
setTimer(number);
if(number === MAX_TIMER) {
clearInterval(tickIntervalId.current);
}
}
let i = 0;
console.log('i is: ', i) // i value is 0 every render
const incrementNumber = () => {
i += 1;
console.log('in incrementNumber, i is: ', i); // i value is incrementing every render: 1, 2, 3, 4, 5. how?
return i;
}
useEffect(() => {
tickIntervalId.current = setInterval(tickTimer, 1000, incrementNumber);
}, [])
return (
<div>
{timer}
</div>
);
}
问题是 - 为什么这个秒表会按预期工作?
因为每一秒我都会重新渲染组件,let i = 0
行会再次执行,所以我希望
incrementNumber
的返回值将始终为 1 (0 + 1)
但相反,返回值递增:1、2、3、4、5。
这是控制台输出:
i is: 0
in incrementNumber, i is: 1
i is: 0
in incrementNumber, i is: 2
i is: 0
in incrementNumber, i is: 3
i is: 0
in incrementNumber, i is: 4
i is: 0
in incrementNumber, i is: 5
i is: 0
您需要了解词法环境和执行上下文以及范围如何工作
这里已经讨论过了
几乎相似的例子
我认为这是一个范围问题,如果你在 useEffect 中给出 console.log (i),我们可以看到它只在第一次渲染时记录。好像 useEffect 中的 let i 与组件的不同。即使使用新的渲染,useEffect 中的状态仍然存在。
useEffect(() => {
console.log("i in effect: ", i)
tickIntervalId.current = setInterval(tickTimer, 1000, incrementNumber);
}, []);
下面是一个演示词法范围问题的小例子。
function myFn() {
let i = 0; // `i` is local to myFn() (has local scope)
console.log(`myFn i == ${i}`);
return (caller) => {
i++;
console.log(`${caller} : i == ${i}`);
}
}
let a = myFn(); // 'myFn i == 0'
a('a'); // 'a : i == 1'
let b = myFn(); // 'myFn i == 0'
b('b'); // 'b : i == 1'
b('b'); // 'b : i == 2'
b('b'); // 'b : i == 3'
a('a'); // 'a : i == 2'
@Ido
这里 link 更有意义:我正在使用一个对象,因此您可以看到变量的存储位置。我也赞成所有其他答案,因为它们是正确的。这是范围问题,而不是 React 问题。
https://codesandbox.io/s/material-demo-wj0pm
import React, { useEffect, useState, useRef } from "react";
const MAX_TIMER = 5;
let renderCount = 0;
export default function StopWatch() {
const [timer, setTimer] = useState({});
const tickIntervalId = useRef(null);
const tickTimer = getTimerTimeFn => {
const number = getTimerTimeFn();
setTimer({...number});
if (number === MAX_TIMER) {
clearInterval(tickIntervalId.current);
}
};
var iObject = {
i: 0,
inRender: renderCount,
};
renderCount++;
console.log("i is: ", JSON.stringify(iObject) ); // i value is 0 every render
const incrementNumber = () => {
iObject.i += 1;
console.log("in incrementNumber, i is: ", JSON.stringify(iObject)); // i value is incrementing every render: 1, 2, 3, 4, 5. how?
console.log("------------------------");
return iObject; //Not a copy
};
useEffect(() => {
tickIntervalId.current = setInterval(tickTimer, 1000, incrementNumber);
}, []);
return <div>{JSON.stringify(timer)}</div>;
}