带有 setTimeout 的 React,变速时钟
React, variable speed Clock with setTimeout
目标:创建一个Clock
定时调用回调方法,速度可控的组件
棘手的部分:当速度改变时不要立即重置时钟计时器,但在下一个“滴答”检查所需的速度,如果它有变化,重置当前间隔并安排一个新的。这是在更改速度时保持时钟票平稳运行所必需的。
我认为传递一个 getDelay
函数 returns 延迟(而不是延迟本身的值)可以使这项工作成功,但事实并非如此。
如果我让 useEffect
跟踪 getDelay
函数,它将在延迟更改时重置。如果它不跟踪 getDelay
当时钟为 运行.
时速度不会改变
import React, { useEffect, useRef } from "react";
type Callback = () => void;
function useInterval(tickCallback: Callback, getDelay: () => number, isPlaying: boolean) {
const refDelay = useRef<number>(getDelay());
useEffect(() => {
let id: number;
console.log(`run useEffects`);
function tick() {
const newDelay = getDelay();
if (tickCallback) {
console.log(`newDelay: ${newDelay}`);
tickCallback();
if (newDelay !== refDelay.current) {
// if delay has changed, clear and schedule new interval
console.log(`delay changed. was ${refDelay.current} now is ${newDelay}`)
refDelay.current = newDelay;
clear();
playAndSchedule(newDelay);
}
}
}
/** clear interval, if any */
function clear() {
if (id) {
console.log(`clear ${id}`)
clearInterval(id);
}
}
/** schedule interval and return cleanup function */
function playAndSchedule(delay: number) {
if (isPlaying) {
id = window.setInterval(tick, delay);
console.log(`schedule delay id ${id}. ms ${delay}`)
return clear
}
}
return playAndSchedule(refDelay.current);
},
// with getDelay here the clock is reset as soon as the delay value changes
[isPlaying, getDelay]);
}
type ClockProps = {
/** true if playing */
isPlaying: boolean;
/** return the current notes per minute */
getNpm: () => number;
/** function to be executed every tick */
callback: () => void;
}
export function Clock(props: ClockProps) {
const { isPlaying, getNpm, callback } = props;
useInterval(
callback,
() => {
console.log(`compute delay for npm ${getNpm()}`);
return 60_000 / getNpm();
},
isPlaying);
return (<React.Fragment />);
}
你可以使用这样的东西:
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
function useInterval(tickCallback: () => void, delay: number, isPlaying: boolean) {
const timeout = useRef<any>(null);
const savedDelay = useRef(delay);
const savedTickCallback = useRef(tickCallback);
useEffect(() => {
savedDelay.current = delay;
}, [delay])
useEffect(() => {
savedTickCallback.current = tickCallback;
}, [tickCallback])
const startTimeout = useCallback(() => {
const delay = savedDelay.current;
console.log('next delay', delay);
timeout.current = setTimeout(() => {
console.log('delay done', delay);
savedTickCallback.current();
startTimeout();
}, savedDelay.current);
}, []);
useEffect(() => {
if (isPlaying) {
if (!timeout.current) {
startTimeout();
}
} else {
if (timeout.current) {
clearTimeout(timeout.current);
}
}
},
[isPlaying, startTimeout],
);
}
type ClockProps = {
/** true if playing */
isPlaying: boolean;
/** return the current notes per minute */
getNpm: () => number;
/** function to be executed every tick */
callback: () => void;
}
export const Clock: React.FC<ClockProps> = ({ isPlaying, getNpm, callback }) => {
const delay = useMemo(() => {
console.log(`compute delay for npm ${getNpm()}`);
return 60_000 / getNpm();
}, [getNpm]);
useInterval(callback, delay, isPlaying);
return null;
};
目标:创建一个Clock
定时调用回调方法,速度可控的组件
棘手的部分:当速度改变时不要立即重置时钟计时器,但在下一个“滴答”检查所需的速度,如果它有变化,重置当前间隔并安排一个新的。这是在更改速度时保持时钟票平稳运行所必需的。
我认为传递一个 getDelay
函数 returns 延迟(而不是延迟本身的值)可以使这项工作成功,但事实并非如此。
如果我让 useEffect
跟踪 getDelay
函数,它将在延迟更改时重置。如果它不跟踪 getDelay
当时钟为 运行.
import React, { useEffect, useRef } from "react";
type Callback = () => void;
function useInterval(tickCallback: Callback, getDelay: () => number, isPlaying: boolean) {
const refDelay = useRef<number>(getDelay());
useEffect(() => {
let id: number;
console.log(`run useEffects`);
function tick() {
const newDelay = getDelay();
if (tickCallback) {
console.log(`newDelay: ${newDelay}`);
tickCallback();
if (newDelay !== refDelay.current) {
// if delay has changed, clear and schedule new interval
console.log(`delay changed. was ${refDelay.current} now is ${newDelay}`)
refDelay.current = newDelay;
clear();
playAndSchedule(newDelay);
}
}
}
/** clear interval, if any */
function clear() {
if (id) {
console.log(`clear ${id}`)
clearInterval(id);
}
}
/** schedule interval and return cleanup function */
function playAndSchedule(delay: number) {
if (isPlaying) {
id = window.setInterval(tick, delay);
console.log(`schedule delay id ${id}. ms ${delay}`)
return clear
}
}
return playAndSchedule(refDelay.current);
},
// with getDelay here the clock is reset as soon as the delay value changes
[isPlaying, getDelay]);
}
type ClockProps = {
/** true if playing */
isPlaying: boolean;
/** return the current notes per minute */
getNpm: () => number;
/** function to be executed every tick */
callback: () => void;
}
export function Clock(props: ClockProps) {
const { isPlaying, getNpm, callback } = props;
useInterval(
callback,
() => {
console.log(`compute delay for npm ${getNpm()}`);
return 60_000 / getNpm();
},
isPlaying);
return (<React.Fragment />);
}
你可以使用这样的东西:
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
function useInterval(tickCallback: () => void, delay: number, isPlaying: boolean) {
const timeout = useRef<any>(null);
const savedDelay = useRef(delay);
const savedTickCallback = useRef(tickCallback);
useEffect(() => {
savedDelay.current = delay;
}, [delay])
useEffect(() => {
savedTickCallback.current = tickCallback;
}, [tickCallback])
const startTimeout = useCallback(() => {
const delay = savedDelay.current;
console.log('next delay', delay);
timeout.current = setTimeout(() => {
console.log('delay done', delay);
savedTickCallback.current();
startTimeout();
}, savedDelay.current);
}, []);
useEffect(() => {
if (isPlaying) {
if (!timeout.current) {
startTimeout();
}
} else {
if (timeout.current) {
clearTimeout(timeout.current);
}
}
},
[isPlaying, startTimeout],
);
}
type ClockProps = {
/** true if playing */
isPlaying: boolean;
/** return the current notes per minute */
getNpm: () => number;
/** function to be executed every tick */
callback: () => void;
}
export const Clock: React.FC<ClockProps> = ({ isPlaying, getNpm, callback }) => {
const delay = useMemo(() => {
console.log(`compute delay for npm ${getNpm()}`);
return 60_000 / getNpm();
}, [getNpm]);
useInterval(callback, delay, isPlaying);
return null;
};