问题
前端群里有个鲨鱼巨佬秀了一波定时器的代码,然后群里展开了讨论,在 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,用于在组件渲染后执行副作用,可以在组件渲染前执行清理函数。