0%

React 中使用定时器

问题

前端群里有个鲨鱼巨佬秀了一波定时器的代码,然后群里展开了讨论,在 React 中使用定时器其实并没有那么简单,因为你不单单要考虑定时器的问题,还要考虑性能问题。

先贴一波大佐巨佬的代码,鲨鱼的代码没找到。

App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
* {
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

1
2
3
4
5
6
7
8
9
10
11
12
13
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,用于在下一次重绘之前执行回调函数,这个回调函数会在浏览器重绘之前执行,这样可以保证动画的流畅性。

MDN - requestAnimationFrame

performance.now()

performance.now() 是一个浏览器提供的 API,用于获取当前时间,返回的是一个浮点数,单位是毫秒。

MDN - performance.now()

useMeno

useMemo 是一个 React Hook,用于缓存计算结果,只有当依赖项发生变化时才会重新计算。

React - useMemo

useRef

useRef 是一个 React Hook,用于创建一个可变的 ref 对象,这个对象在组件的整个生命周期内保持不变。

React - useRef

useCallback

useCallback 是一个 React Hook,用于缓存函数,只有当依赖项发生变化时才会重新创建函数。

React - useCallback

useEffect

useEffect 是一个 React Hook,用于在组件渲染后执行副作用,可以在组件卸载前执行清理函数。

React - useEffect

总结

本文介绍了如何使用 React Hook 实现一个简单的计时器,通过这个例子,我们可以学习到如何使用 requestAnimationFrameperformance.now()useMemouseRefuseCallbackuseEffect 等 React Hook。

参考

React - Hooks API Reference

不错的项目

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,用于在组件渲染后执行副作用,可以在组件渲染前执行清理函数。

React - useLayoutEffect

相关文章