RTW*_*RTW 18 javascript settimeout reactjs react-hooks
我不明白为什么当我使用setTimeout函数时我的react组件启动到无限的console.log.一切正常,但PC开始落后于地狱.有些人说超时功能会改变我的状态和rerender组件,设置新的计时器等等.现在我需要了解如何清除它是正确的.
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
}
Run Code Online (Sandbox Code Playgroud)
清除不同版本的代码无助于:
const [showLoading, setShowLoading] = useState(true)
let timer1 = setTimeout(() => setShowLoading(true), 1000)
useEffect(
() => {
return () => {
clearTimeout(timer1)
}
},
[showLoading]
)
Run Code Online (Sandbox Code Playgroud)
RTW*_*RTW 32
这是一种使用和清除超时的工作方式:
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>
}
Run Code Online (Sandbox Code Playgroud)
Dan*_*ger 32
问题是你在调用setTimeoutexternal useEffect,所以每次渲染组件时都会设置一个新的超时时间,最终会被再次调用并改变状态,迫使组件再次重新渲染,这将设置一个新的超时时间,从而...
因此,正如您已经发现的,使用setTimeout或setInterval使用钩子的方法是将它们包裹在 中useEffect,如下所示:
React.useEffect(() => {
const timeoutID = window.setTimeout(() => {
...
}, 1000);
return () => window.clearTimeout(timeoutID );
}, []);
Run Code Online (Sandbox Code Playgroud)
因为deps = [],useEffect的回调只会被调用一次。然后,您返回的回调将在组件卸载时调用。
无论如何,我鼓励您创建自己的useTimeout钩子,以便您可以通过使用setTimeout declaratively来干燥和简化代码,正如 Dan AbramovsetInterval在使用 React Hooks 制作 setInterval Declarative 中所建议的那样,这非常相似:
React.useEffect(() => {
const timeoutID = window.setTimeout(() => {
...
}, 1000);
return () => window.clearTimeout(timeoutID );
}, []);
Run Code Online (Sandbox Code Playgroud)
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'));Run Code Online (Sandbox Code Playgroud)
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) }
}Run Code Online (Sandbox Code Playgroud)
除了生成更简单和更清晰的代码之外,这还允许您通过传递自动清除超时delay = null并返回超时 ID,以防您想手动取消它(这在 Dan 的帖子中没有涉及)。
如果您正在寻找类似的答案setInterval而不是setTimeout,请查看:https : //stackoverflow.com/a/59274004/3723993。
您还可以在https://gist.github.com/Danziger/336e75b6675223ad805a88c2dfdcfd4a 中找到setTimeoutand setInterval、useTimeoutand 和用 TypeScript 编写useInterval的自定义useThrottledCallback钩子的声明版本。
Gab*_*son 11
我写了一个反应钩子,再也不用处理超时了。工作原理与 React.useState() 类似:
新答案
const [showLoading, setShowLoading] = useTimeoutState(false)
// sets loading to true for 1000ms, then back to false
setShowLoading(true, { timeout: 1000})
Run Code Online (Sandbox Code Playgroud)
export const useTimeoutState = <T>(
defaultState: T
): [T, (action: SetStateAction<T>, opts?: { timeout: number }) => void] => {
const [state, _setState] = useState<T>(defaultState);
const [currentTimeoutId, setCurrentTimeoutId] = useState<
NodeJS.Timeout | undefined
>();
const setState = useCallback(
(action: SetStateAction<T>, opts?: { timeout: number }) => {
if (currentTimeoutId != null) {
clearTimeout(currentTimeoutId);
}
_setState(action);
const id = setTimeout(() => _setState(defaultState), opts?.timeout);
setCurrentTimeoutId(id);
},
[currentTimeoutId, defaultState]
);
return [state, setState];
};
Run Code Online (Sandbox Code Playgroud)
旧答案
const [showLoading, setShowLoading] = useTimeoutState(false, {timeout: 5000})
// will set show loading after 5000ms
setShowLoading(true)
// overriding and timeouts after 1000ms
setShowLoading(true, { timeout: 1000})
Run Code Online (Sandbox Code Playgroud)
设置多个状态将刷新超时,并且将在上次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]
}
Run Code Online (Sandbox Code Playgroud)
打字稿:
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
]
}```
Run Code Online (Sandbox Code Playgroud)
如果您的超时位于“if 构造”中,请尝试以下操作:
useEffect(() => {
let timeout;
if (yourCondition) {
timeout = setTimeout(() => {
// your code
}, 1000);
} else {
// your code
}
return () => {
clearTimeout(timeout);
};
}, [yourDeps]);
Run Code Online (Sandbox Code Playgroud)
export const useTimeout = () => {
const timeout = useRef();
useEffect(
() => () => {
if (timeout.current) {
clearTimeout(timeout.current);
timeout.current = null;
}
},
[],
);
return timeout;
};
Run Code Online (Sandbox Code Playgroud)
您可以使用简单的钩子来共享超时逻辑。
const timeout = useTimeout();
timeout.current = setTimeout(your conditions)
Run Code Online (Sandbox Code Playgroud)
每10秒触发一次api:
useEffect(() => {
const timer = window.setInterval(() => {
// function of api call
}, 1000);
return () => {
window.clearInterval(timer);
}
}, [])
Run Code Online (Sandbox Code Playgroud)
如果任何状态发生变化:
useEffect(() => {
// add condition to state if needed
const timer = window.setInterval(() => {
// function of api call
}, 1000);
return () => {
window.clearInterval(timer);
}
}, [state])
Run Code Online (Sandbox Code Playgroud)
您的计算机滞后是因为您可能忘记将空数组作为第二个参数传入useEffect并setState在回调中触发 a 。这会导致无限循环,因为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")
);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)