我不明白为什么当我使用setTimeout
函数时,我的react组件开始进入无限console.log。一切正常,但PC开始陷入困境。
有人说超时中的功能会更改我的状态,并会重新渲染组件,设置新的计时器等。现在,我需要了解如何清除它。
export default function Loading() {
// if data fetching is slow, after 1 sec i will show some loading animation
const [showLoading, setShowLoading] = useState(true)
let timer1 = setTimeout(() => setShowLoading(true), 1000)
console.log('this message will render every second')
return 1
}
清除不同版本的代码无济于事:
const [showLoading, setShowLoading] = useState(true)
let timer1 = setTimeout(() => setShowLoading(true), 1000)
useEffect(
() => {
return () => {
clearTimeout(timer1)
}
},
[showLoading]
)
答案 0 :(得分:19)
useEffect
时,useEffect
中的 Return 函数都会运行(第一次运行是在组件安装时除外)。考虑一下它,因为每次执行新的useEffect
时,旧的都会被删除。
export default function Loading() {
const [showLoading, setShowLoading] = useState(false)
useEffect(
() => {
let timer1 = setTimeout(() => setShowLoading(true), 1000)
// this will clear Timeout when component unmont like in willComponentUnmount
return () => {
clearTimeout(timer1)
}
},
[] //useEffect will run only one time
//if you pass a value to array, like this [data] than clearTimeout will run every time this value changes (useEffect re-run)
)
return showLoading && <div>I will be visible after ~1000ms</div>
}
export default function Loading() {
const [showLoading, setShowLoading] = useState(false)
const timerToClearSomewhere = useRef(false) //now you can pass timer to another component
useEffect(
() => {
timerToClearSomewhere.current = setInterval(() => setShowLoading(true), 1000)
return () => {
clearInterval(timerToClearSomewhere.current)
}
},
[]
)
//here we can imitate clear from somewhere else place
useEffect(() => {
setTimeout(() => clearInterval(timerToClearSomewhere.current), 15000)
}, [])
return showLoading && <div>I will be visible after ~1000ms</div>
}
答案 1 :(得分:4)
您的计算机处于滞后状态,因为您可能忘记了将空数组作为useEffect
的第二个参数传递,并在回调中触发了setState
。这会导致无限循环,因为useEffect
在渲染时被触发。
这是在挂载时设置计时器并在卸载时清除计时器的一种有效方法:
function App() {
React.useEffect(() => {
const timer = window.setInterval(() => {
console.log('1 second has passed');
}, 1000);
return () => { // Return callback to run on unmount.
window.clearInterval(timer);
};
}, []); // Pass in empty array to run useEffect only on mount.
return (
<div>
Timer Example
</div>
);
}
ReactDOM.render(
<div>
<App />
</div>,
document.querySelector("#app")
);
<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>
答案 2 :(得分:1)
问题是您在setTimeout
之外调用useEffect
,因此每次呈现组件时都要设置一个新的超时,该超时最终将再次被调用并更改状态,从而迫使该组件再次重新渲染,这将设置一个新的时间,
因此,正如您已经发现的那样,将setTimeout
或setInterval
与钩子一起使用的方法是将它们包装在useEffect
中,如下所示:
React.useEffect(() => {
const timeoutID = window.setTimeout(() => {
...
}, 1000);
return () => window.clearInterval(timeoutID );
}, []);
与deps = []
一样,useEffect
的回调仅被调用一次。然后,卸载组件时将调用您返回的回调。
无论如何,我鼓励您创建自己的useTimeout
挂钩,以便您可以使用setTimeout
declaratively进行干燥和简化代码,如Dan Abramov建议的{{1} } Making setInterval Declarative with React Hooks中的},
setInterval
function useTimeout(callback, delay) {
const timeoutRef = React.useRef();
const callbackRef = React.useRef(callback);
// Remember the latest callback:
//
// Without this, if you change the callback, when setTimeout kicks in, it
// will still call your old callback.
//
// If you add `callback` to useEffect's deps, it will work fine but the
// timeout will be reset.
React.useEffect(() => {
callbackRef.current = callback;
}, [callback]);
// Set up the timeout:
React.useEffect(() => {
if (typeof delay === 'number') {
timeoutRef.current = window.setTimeout(() => callbackRef.current(), delay);
// Clear timeout if the components is unmounted or the delay changes:
return () => window.clearTimeout(timeoutRef.current);
}
}, [delay]);
// In case you want to manually clear the timeout from the consuming component...:
return timeoutRef;
}
const App = () => {
const [isLoading, setLoading] = React.useState(true);
const [showLoader, setShowLoader] = React.useState(false);
// Simulate loading some data:
const fakeNetworkRequest = React.useCallback(() => {
setLoading(true);
setShowLoader(false);
// 50% of the time it will display the loder, and 50% of the time it won't:
window.setTimeout(() => setLoading(false), Math.random() * 4000);
}, []);
// Initial data load:
React.useEffect(fakeNetworkRequest, []);
// After 2 second, we want to show a loader:
useTimeout(() => setShowLoader(true), isLoading ? 2000 : null);
return (<React.Fragment>
<button onClick={ fakeNetworkRequest } disabled={ isLoading }>
{ isLoading ? 'LOADING... ?' : 'LOAD MORE ?' }
</button>
{ isLoading && showLoader ? <div className="loader"><span className="loaderIcon">?</span></div> : null }
{ isLoading ? null : <p>Loaded! ✨</p> }
</React.Fragment>);
}
ReactDOM.render(<App />, document.querySelector('#app'));
body,
button {
font-family: monospace;
}
body, p {
margin: 0;
}
#app {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
button {
margin: 32px 0;
padding: 8px;
border: 2px solid black;
background: transparent;
cursor: pointer;
border-radius: 2px;
}
.loader {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-size: 128px;
background: white;
}
.loaderIcon {
animation: spin linear infinite .25s;
}
@keyframes spin {
from { transform:rotate(0deg) }
to { transform:rotate(360deg) }
}
除了生成更简单,更简洁的代码外,它还允许您通过传递<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>
自动清除超时,并返回超时ID,以防您要手动取消超时(Dan的帖子中未涉及)
如果您正在寻找delay = null
而不是setInterval
的类似答案,请查看以下内容:https://stackoverflow.com/a/59274004/3723993。
您还可以找到setTimeout
和setTimeout
,setInterval
和useTimeout
的声明性版本,以及在{{3 }}。
答案 3 :(得分:0)
我写了一个React Hook,不再需要处理超时。 就像React.useState()一样工作,但是会超时默认为初始值,在这种情况下为false:
const [showLoading, setShowLoading] = useTimeoutState(false, {timeout: 5000})
您还可以在特定的setStates
上覆盖此超时时间:
const [showLoading, setShowLoading] = useTimeoutState(false, {timeout: 5000}) // can also not pass any timeout here
setShowLoading(true, {timeout: 1000}) // timeouts after 1000ms instead of 5000ms
设置多个状态只会刷新该功能,并且它将在与上一个setState
设置相同的毫秒后超时。
Vanilla js(未经测试,打字稿版本为)
import React from "react"
// sets itself automatically to default state after timeout MS. good for setting timeouted states for risky requests etc.
export const useTimeoutState = (defaultState, opts) => {
const [state, _setState] = React.useState(defaultState)
const [currentTimeoutId, setCurrentTimeoutId] = React.useState()
const setState = React.useCallback(
(newState: React.SetStateAction, setStateOpts) => {
clearTimeout(currentTimeoutId) // removes old timeouts
newState !== state && _setState(newState)
if (newState === defaultState) return // if already default state, no need to set timeout to set state to default
const id = setTimeout(
() => _setState(defaultState),
setStateOpts?.timeout || opts?.timeout
)
setCurrentTimeoutId(id)
},
[currentTimeoutId, state, opts, defaultState]
)
return [state, setState]
}
打字稿:
import React from "react"
interface IUseTimeoutStateOptions {
timeout?: number
}
// sets itself automatically to default state after timeout MS. good for setting timeouted states for risky requests etc.
export const useTimeoutState = <T>(defaultState: T, opts?: IUseTimeoutStateOptions) => {
const [state, _setState] = React.useState<T>(defaultState)
const [currentTimeoutId, setCurrentTimeoutId] = React.useState<number | undefined>()
// todo: change any to React.setStateAction with T
const setState = React.useCallback(
(newState: React.SetStateAction<any>, setStateOpts?: { timeout?: number }) => {
clearTimeout(currentTimeoutId) // removes old timeouts
newState !== state && _setState(newState)
if (newState === defaultState) return // if already default state, no need to set timeout to set state to default
const id = setTimeout(
() => _setState(defaultState),
setStateOpts?.timeout || opts?.timeout
) as number
setCurrentTimeoutId(id)
},
[currentTimeoutId, state, opts, defaultState]
)
return [state, setState] as [
T,
(newState: React.SetStateAction<T>, setStateOpts?: { timeout?: number }) => void
]
}```
答案 4 :(得分:0)
const[seconds, setSeconds] = useState(300);
function TimeOut() {
useEffect(() => {
let interval = setInterval(() => {
setSeconds(seconds => seconds -1);
}, 1000);
return() => clearInterval(interval);
}, [])
function reset() {
setSeconds(300);
}
return (
<div>
Count Down: {seconds} left
<button className="button" onClick={reset}>
Reset
</button>
</div>
)
}
确保导入useState和useEffect。另外,添加逻辑以将计时器停止为0。
答案 5 :(得分:0)
如果你想制作一个像“开始”这样的按钮,那么使用“useInterval”钩子可能不合适,因为除了在组件顶部之外,react 不允许你调用钩子。
export default function Loading() {
// if data fetching is slow, after 1 sec i will show some loading animation
const [showLoading, setShowLoading] = useState(true)
const interval = useRef();
useEffect(() => {
interval.current = () => setShowLoading(true);
}, [showLoading]);
// make a function like "Start"
// const start = setInterval(interval.current(), 1000)
setInterval(() => interval.current(), 1000);
console.log('this message will render every second')
return 1
}
答案 6 :(得分:0)
如果您的超时在“如果构造”中,请尝试以下操作:
useEffect(() => {
let timeout;
if (yourCondition) {
timeout = setTimeout(() => {
// your code
}, 1000);
} else {
// your code
}
return () => {
clearTimeout(timeout);
};
}, [yourDeps]);