Yan*_*Tay 55 javascript reactjs react-hooks
我正在尝试新的React Hooks并且有一个带有计数器的Clock组件,它应该每秒都会增加.但是,该值不会超过一个.
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));Run Code Online (Sandbox Code Playgroud)
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>Run Code Online (Sandbox Code Playgroud)
Yan*_*Tay 76
原因是因为传递给setInterval闭包的回调只访问time第一个渲染中的变量,它不能访问time后续渲染中的新值,因为useEffect()第二次没有调用它.
time在setInterval回调中始终具有值0 .
就像setState你熟悉的那样,状态钩子有两种形式:一种是它处于更新状态,另一种是传递当前状态的回调形式.你应该使用第二种形式并在setState回调中读取最新的状态值在增加之前确保您具有最新的状态值.
奖金:替代方法
Dan Abramov深入探讨了
setInterval在博客文章中使用钩子的主题,并提供了解决此问题的替代方法.强烈推荐阅读!
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(prevTime => prevTime + 1); // <-- Change this line!
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));Run Code Online (Sandbox Code Playgroud)
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>Run Code Online (Sandbox Code Playgroud)
Dan*_*ger 27
正如其他人所指出的,问题是useState只调用一次 (as deps = []) 来设置间隔:
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => window.clearInterval(timer);
}, []);
Run Code Online (Sandbox Code Playgroud)
然后,每次setInterval滴答时,它实际上会调用setTime(time + 1),但time将始终保持setInterval定义回调(闭包)时的初始值。
您可以使用useState's setter的替代形式并提供回调而不是您要设置的实际值(就像 with setState):
setTime(prevTime => prevTime + 1);
Run Code Online (Sandbox Code Playgroud)
但我鼓励您创建自己的useInterval钩子,以便您可以通过setInterval 声明式使用来 DRY 和简化代码,正如 Dan Abramov 在使用 React Hooks 创建 setInterval Declarative 中所建议的那样:
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => window.clearInterval(timer);
}, []);
Run Code Online (Sandbox Code Playgroud)
setTime(prevTime => prevTime + 1);
Run Code Online (Sandbox Code Playgroud)
function useInterval(callback, delay) {
const intervalRef = React.useRef();
const callbackRef = React.useRef(callback);
// Remember the latest callback:
//
// Without this, if you change the callback, when setInterval ticks again, it
// will still call your old callback.
//
// If you add `callback` to useEffect's deps, it will work fine but the
// interval will be reset.
React.useEffect(() => {
callbackRef.current = callback;
}, [callback]);
// Set up the interval:
React.useEffect(() => {
if (typeof delay === 'number') {
intervalRef.current = window.setInterval(() => callbackRef.current(), delay);
// Clear interval if the components is unmounted or the delay changes:
return () => window.clearInterval(intervalRef.current);
}
}, [delay]);
// Returns a ref to the interval ID in case you want to clear it manually:
return intervalRef;
}
const Clock = () => {
const [time, setTime] = React.useState(0);
const [isPaused, setPaused] = React.useState(false);
const intervalRef = useInterval(() => {
if (time < 10) {
setTime(time + 1);
} else {
window.clearInterval(intervalRef.current);
}
}, isPaused ? null : 1000);
return (<React.Fragment>
<button onClick={ () => setPaused(prevIsPaused => !prevIsPaused) } disabled={ time === 10 }>
{ isPaused ? 'RESUME ?' : 'PAUSE ' }
</button>
<p>{ time.toString().padStart(2, '0') }/10 sec.</p>
<p>setInterval { time === 10 ? 'stopped.' : 'running...' }</p>
</React.Fragment>);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));Run Code Online (Sandbox Code Playgroud)
除了生成更简单、更清晰的代码之外,这还允许您通过简单地传递来自动暂停(和清除)间隔delay = null并返回间隔 ID,以防您想手动取消它(这在 Dan 的帖子中没有涉及)。
实际上,这也可以改进,以便它在未delay暂停时不会重新启动,但我想对于大多数用例来说这已经足够了。
如果您正在为setTimeout而寻找类似的答案setInterval,请查看:https : //stackoverflow.com/a/59274757/3723993。
您还可以在https://gist.github.com/Danziger/336e75b6675223ad805a88c2dfdcfd4a 中找到setTimeoutand setInterval、useTimeoutand 和用 TypeScript 编写useInterval的自定义useThrottledCallback钩子的声明版本。
Est*_*ask 11
useEffect 提供空输入列表时,仅在组件安装上评估一次该功能。
另一种方法setInterval是在setTimeout每次更新状态时设置新的时间间隔:
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = setTimeout(() => {
setTime(time + 1);
}, 1000);
return () => {
clearTimeout(timer);
};
}, [time]);
Run Code Online (Sandbox Code Playgroud)
的性能影响setTimeout微不足道,通常可以忽略。除非组件是时间敏感到新设置的超时引起不希望的效应的点,都setInterval和setTimeout方法是可以接受的。
另一种解决方案是使用useReducer,因为它将始终传递当前状态。
function Clock() {
const [time, dispatch] = React.useReducer((state = 0, action) => {
if (action.type === 'add') return state + 1
return state
});
React.useEffect(() => {
const timer = window.setInterval(() => {
dispatch({ type: 'add' });
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));Run Code Online (Sandbox Code Playgroud)
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>Run Code Online (Sandbox Code Playgroud)
useRef 可以解决这个问题,这里有一个类似的组件,每 1000ms 增加一次计数器
import { useState, useEffect, useRef } from "react";
export default function App() {
const initalState = 0;
const [count, setCount] = useState(initalState);
const counterRef = useRef(initalState);
useEffect(() => {
counterRef.current = count;
})
useEffect(() => {
setInterval(() => {
setCount(counterRef.current + 1);
}, 1000);
}, []);
return (
<div className="App">
<h1>The current count is:</h1>
<h2>{count}</h2>
</div>
);
}
Run Code Online (Sandbox Code Playgroud)
我认为这篇文章将帮助你使用间隔来做反应钩子
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds((seconds) => {
if (seconds === 5) {
setSeconds(0);
return clearInterval(interval);
}
return (seconds += 1);
});
}, 1000);
}, []);
Run Code Online (Sandbox Code Playgroud)
注意:这将有助于使用 useState 挂钩更新和重置计数器。秒数将在 5 秒后停止。因为首先更改 setSecond 值,然后停止计时器并在 setInterval 内更新秒数。因为 useEffect 运行一次。
| 归档时间: |
|
| 查看次数: |
16003 次 |
| 最近记录: |