我们在之前的文章 如何构建自己的 react hooks 中实现过 我们再看看之前的 使用 useInterval 来实现下进度的变化: 但使用 useInterval 的话,颗粒度不够细,可能就会存在丢帧的情况。这里我们就要考虑到使用 我们可以仿照 useInterval 的写法来写一个 useAnimationFrame,但要注意的是,setInterval 是启动之后就不用再管了,他会自动按照间隔来执行下一个任务,但 requestAnimationFrame 类似于 setTimeout,每次都要在当前任务里去启动下一个任务,这样就会产生一个新的 requestId(取消时使用),因此这个新的 requestId 也要用一个 ref 来保存: 我们实现了 useAnimationFrame 后,就可以使用这个来实现进度的变化: 我们在 因此,可以基于 useAnimationFrame 封装一个更加复杂的 我们先定义几个 Progress 组件的属性: 我们当前组件其实只需要通过一个 这时,我们就可以编写 如果组件更复杂一些,可以给父级组件暴露几个方法,方便更多的控制,使用 点击查看 Progress 组件的使用:Progress 组件的使用。 若希望更加简洁一些,只想获取当前的进度,可以基于 useAnimationFrame 自定义一个 这里的 useProgress 只是返回了当前的进度,使用起来也非常地方便: 这里我们总结了下 useState 和 requestAnimationFrame 封装的 useAnimationFrame 的 hook,然后通过这个 hook 可以很方便地控制页面中 state 的变化。本来想在 useProgress 中可以根据三次贝塞尔曲线来进行一个进度的曲线变化,但研究了一下发现,三次贝塞尔曲线的坐标(x, y)都是通过变量 t 得到的,这里需要推导出一个 y=f(x)的公式,根据 x 得到 y 的变化,比较麻烦,这里就不展开讲了。useInterval
的自定义 hook。我们有个进度条,需要从进度 0 逐渐增加到固定的进度时,使用 useInterval 就可以实现。1. 复习 useInterval #
useInterval
是怎么实现的。const useInterval = (callback, delay) => {
const saveCallback = useRef();
useEffect(() => {
// 每次渲染后,保存新的回调到我们的 ref 里
saveCallback.current = callback;
});
useEffect(() => {
function tick() {
saveCallback.current();
}
// delay变为null的的时候,会先清除掉之前的定时器
// 然后也不会起新的定时器,整个useInterval结束
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
};
const progress = 76; // 当前的实际进度
const [step, setStep] = useState(0); // 进度的过程
// useInterval怎么实现的可以查看上面的链接
useInterval(
() => {
setStep(step + 1);
},
step < progress ? 20 : null
); // 当为数字时则按照这个数字切换,若为null则停止定时器
requestAnimationFrame
来实现进度的一个变化。2. 实现 useAnimationFrame #
const useAnimationFrame = (callback, running) => {
const savedCallback = useRef(callback); // 传进来的callback
const requestId = useRef(0); // 当前正在执行的requestId
useEffect(() => {
savedCallback.current = callback;
});
useEffect(() => {
function tick() {
savedCallback.current();
if (running) {
// 当running为true时,才启动下一个,并拿到最新的requestId
requestId.current = window.requestAnimationFrame(tick);
}
}
if (running) {
const animationFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame;
const cancelAnimationFrame =
window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame;
requestId.current = animationFrame(tick);
return () => cancelAnimationFrame(requestId.current);
}
}, [running]);
};
const progress = 76; // 当前的实际进度
const [step, setStep] = useState(0); // 进度的过程
useAnimationFrame(() => {
setStep(step + 1);
}, step < progress); // 这里为true和false
useAnimationFrame
的 hook 相比 useInterval,数据变化会更加流畅,点击查看 demo:useAnimationFrame 的使用。3. 基于 useAnimationFrame 实现更多的功能 #
useAnimationFrame
中只是实现了基本的数据变化功能,但实际应用中,还需要更多的功能,例如:
组件和
useProgress
的自定义 hook;3.1 实现 Progress 组件 #
interface ProgressProps {
startNum?: number; // 起始进度
endNum?: number; // 结束的进度
step?: number; // 每一步跳跃的进度
running?: boolean; // 进度是否进行
onStart?: () => void; // 监听开始事件
onStep?: (step: number) => void; // 监听进度变化事件
onEnd?: () => void; // 监听进度结束的事件
}
running
字段来控制进度是否前进。组件了。
const Progress = ({
startNum = 0,
endNum = 100,
step = 1,
running = false,
onStart = () => {}, // 开始的回调
onStep = () => {}, // 每一步的回调
onEnd = () => {}, // 结束时的回调
}: ProgressProps) => {
const [progress, setProgress] = useState < number > startNum;
onStart();
useAnimationFrame(() => {
const nextProgress = Math.min(progress + step, endNum);
if (nextProgress <= endNum) {
setProgress(nextProgress);
onStep(nextProgress);
} else {
onEnd();
}
}, running && progress < endNum);
return <p>{progress}p>;
};
useImperativeHandle
,给传入的 ref 绑定几个方法// cref为传进来的ref对象
useImperativeHandle(cref, () => ({
// 这里是暴露给父组件的方法
// 开始
start() {
setRunning(true);
},
// 暂停
pause() {
setRunning(false);
},
// 切换状态
toggle() {
setRunning(!running);
},
// 重新开始
restart() {
setProgress(startNum);
setRunning(true);
},
}));
3.2 实现 useProgress 的 hook #
useProgress
的 hook:const useProgress = ({
startNum = 0,
endNum = 100,
step = 1,
running = true,
}) => {
const [progress, setProgress] = useState(startNum);
useAnimationFrame(() => {
const nextProgress = Math.min(progress + step, endNum);
setProgress(nextProgress);
}, running && progress < endNum);
return progress;
};
const progress = useProgress({});
return (
<div className="home">
<p>progress: {progress}p>
div>
);
4. 总结 #
版权属于:
加速器之家
作品采用:
《
署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
》许可协议授权
评论