问题
前端群里有个鲨鱼巨佬秀了一波定时器的代码,然后群里展开了讨论,在 React 中使用定时器其实并没有那么简单,因为你不单单要考虑定时器的问题,还要考虑性能问题。
先贴一波大佐巨佬的代码,鲨鱼的代码没找到。
App.js
import { useState, useCallback, useEffect, useMemo, useRef } from "react";
import "./styles.css";
export default function App() {
const phase = useRef("idle");
const base = useRef();
const [btnText, setBtnText] = useState("Start");
const [accTime, setAccTime] = useState(0);
const [elsTime, setElsTime] = useState(0);
const time = useMemo(() => accTime + elsTime, [accTime, elsTime]);
useEffect(() => {
let stopped = false;
(function tick() {
if ("running" === phase.current) {
setElsTime(performance.now() - base.current);
}
!stopped && requestAnimationFrame(tick);
})();
return () => (stopped = true);
}, []);
const onClickButton = useCallback(() => {
phase.current = {
idle: "running",
paused: "running",
running: "paused"
}[phase.current];
setBtnText(
{
idle: "Start",
paused: "Resume",
running: "Pause"
}[phase.current]
);
if ("running" === phase.current) {
base.current = performance.now();
} else if ("paused" === phase.current) {
setAccTime(time);
setElsTime(0);
}
}, [time]);
return (
<div className="App">
<h1>{(time * 1e-3).toFixed(3)}</h1>
<button onClick={onClickButton}>{btnText}</button>
</div>
);
}
styles.css
* {
box-sizing: border-box;
}
body {
background-color: #444;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
}
h1 {
background-color: #222;
color: white;
padding: 12px 20px;
min-width: 200px;
text-align: center;
}
button {
padding: 8px 32px;
border: none;
background-color: slateblue;
color: white;
cursor: pointer;
min-width: 200px;
font-size: 16px;
font-family: "Trebuchet MS", "Lucida Sans Unicode", "Lucida Grande",
"Lucida Sans", Arial, sans-serif;
}
index.js
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<StrictMode>
<App />
</StrictMode>
);
知识点
requestAnimationFrame
requestAnimationFrame
是一个浏览器提供的 API,用于在下一次重绘之前执行回调函数,这个回调函数会在浏览器重绘之前执行,这样可以保证动画的流畅性。
performance.now()
performance.now()
是一个浏览器提供的 API,用于获取当前时间,返回的是一个浮点数,单位是毫秒。
useMeno
useMemo
是一个 React Hook,用于缓存计算结果,只有当依赖项发生变化时才会重新计算。
useRef
useRef
是一个 React Hook,用于创建一个可变的 ref 对象,这个对象在组件的整个生命周期内保持不变。
useCallback
useCallback
是一个 React Hook,用于缓存函数,只有当依赖项发生变化时才会重新创建函数。
useEffect
useEffect
是一个 React Hook,用于在组件渲染后执行副作用,可以在组件卸载前执行清理函数。
总结
本文介绍了如何使用 React Hook 实现一个简单的计时器,通过这个例子,我们可以学习到如何使用 requestAnimationFrame
、performance.now()
、useMemo
、useRef
、useCallback
、useEffect
等 React Hook。
参考
不错的项目
https://github.com/ja-klaudiusz/request-animation-frame-hook
上面这个项目
https://github.com/ja-klaudiusz/request-animation-frame-hook/blob/main/src/index.js#L63
不知道为什么用 setTimeout 执行 callback,而不是直接执行 callback ?
https://stackoverflow.com/questions/18509507/why-use-settimeout-in-deferred
这个项目中还用到了 useLayoutEffect
useLayoutEffect
useLayoutEffect
是一个 React Hook,用于在组件渲染后执行副作用,可以在组件渲染前执行清理函数。