一、官方介绍
Github 地址
react-use 是一个流行的 React 自定义 Hook 库,提供了一组常用的 Hook,以帮助开发者在 React 应用程序中更方便地处理常见的任务和功能。
官方将 react-use 的 Hook 分成了以下几个主要类别,以便更好地组织和查找常用的功能。每个类别涵盖了不同类型的 Hook,满足各种开发需求。以下是这些类别的详细说明:
- Sensors
功能: 主要涉及与浏览器或用户交互相关的传感器功能。
示例:
useMouse: 获取鼠标位置。
useWindowSize: 获取窗口尺寸。
useBattery: 监控电池状态。 - UI
功能: 涉及用户界面相关的功能,如处理样式、显示和隐藏元素等。
示例:
useClickAway: 监听点击事件以检测用户点击是否发生在组件外部。
useMeasure: 测量元素的大小和位置。
useDarkMode: 管理和检测暗模式状态。 - Animations
功能: 处理动画和过渡效果。
示例:
useSpring: 使用 react-spring 处理动画效果。
useTransition: 使用 react-spring 处理过渡动画。 - Side-Effects
功能: 处理副作用相关的 Hook,包括数据获取、异步操作等。
示例:
useAsync: 处理异步操作,如数据获取,并提供状态和结果。
useFetch: 简化数据获取操作。
useAxios: 使用 Axios 进行数据请求的 Hook。 - Lifecycles
功能: 处理组件生命周期相关的 Hook。
示例:
useMount: 在组件挂载时执行的 Hook。
useUnmount: 在组件卸载时执行的 Hook。
useUpdate: 在组件更新时执行的 Hook。 - State
功能: 管理组件状态和相关逻辑。
示例:
useState: 提供基本状态管理功能。
useReducer: 替代 useState 实现更复杂的状态逻辑。
useForm: 管理表单状态和验证。
useInput: 管理输入字段的状态。 - Miscellaneous
功能: 各种其他实用功能的 Hook,涵盖一些不容易归类到其他类别的功能。
这种分类方法使得 react-use 的 Hook 更加有组织和易于查找,帮助开发者快速找到需要的功能并有效地集成到他们的应用程序中。
二、源码学习
示例:n. xx - yy
something
使用
源码
解释
UI - useAudio
plays audio and exposes its controls.
使用
import {useAudio} from 'react-use';
const Demo = () => {
const [audio, state, controls, ref] = useAudio({
src: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3',
autoPlay: true,
});
return (
{audio}
{JSON.stringify(state, null, 2)}
);
};
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
源码
import createHTMLMediaHook from './factory/createHTMLMediaHook';
const useAudio = createHTMLMediaHook('audio');
export default useAudio;
1
2
3
4
//./factory/createHTMLMediaHook
import * as React from 'react';
import { useEffect, useRef } from 'react';
import useSetState from '../useSetState';
import parseTimeRanges from '../misc/parseTimeRanges';
export interface HTMLMediaProps
extends React.AudioHTMLAttributes,
React.VideoHTMLAttributes {
src: string;
}
export interface HTMLMediaState {
buffered: any[];
duration: number;
paused: boolean;
muted: boolean;
time: number;
volume: number;
playing: boolean;
}
export interface HTMLMediaControls {
play: () => Promise | void;
pause: () => void;
mute: () => void;
unmute: () => void;
volume: (volume: number) => void;
seek: (time: number) => void;
}
type MediaPropsWithRef = HTMLMediaProps & { ref?: React.MutableRefObject };
export default function createHTMLMediaHook(
tag: 'audio' | 'video'
) {
return (elOrProps: HTMLMediaProps | React.ReactElement) => {
let element: React.ReactElement> | undefined;
let props: MediaPropsWithRef;
if (React.isValidElement(elOrProps)) {
element = elOrProps;
props = element.props;
} else {
props = elOrProps;
}
const [state, setState] = useSetState<HTMLMediaState>({
buffered: [],
time: 0,
duration: 0,
paused: true,
muted: false,
volume: 1,
playing: false,
});
const ref = useRef<T | null>(null);
const wrapEvent = (userEvent, proxyEvent?) => {
return (event) => {
try {
proxyEvent && proxyEvent(event);
} finally {
userEvent && userEvent(event);
}
};
};
const onPlay = () => setState({ paused: false });
const onPlaying = () => setState({ playing: true });
const onWaiting = () => setState({ playing: false });
const onPause = () => setState({ paused: true, playing: false });
const onVolumeChange = () => {
const el = ref.current;
if (!el) {
return;
}
setState({
muted: el.muted,
volume: el.volume,
});
};
const onDurationChange = () => {
const el = ref.current;
if (!el) {
return;
}
const { duration, buffered } = el;
setState({
duration,
buffered: parseTimeRanges(buffered),
});
};
const onTimeUpdate = () => {
const el = ref.current;
if (!el) {
return;
}
setState({ time: el.currentTime });
};
const onProgress = () => {
const el = ref.current;
if (!el) {
return;
}
setState({ buffered: parseTimeRanges(el.buffered) });
};
if (element) {
element = React.cloneElement(element, {
controls: false,
...props,
ref,
onPlay: wrapEvent(props.onPlay, onPlay),
onPlaying: wrapEvent(props.onPlaying, onPlaying),
onWaiting: wrapEvent(props.onWaiting, onWaiting),
onPause: wrapEvent(props.onPause, onPause),
onVolumeChange: wrapEvent(props.onVolumeChange, onVolumeChange),
onDurationChange: wrapEvent(props.onDurationChange, onDurationChange),
onTimeUpdate: wrapEvent(props.onTimeUpdate, onTimeUpdate),
onProgress: wrapEvent(props.onProgress, onProgress),
});
} else {
element = React.createElement(tag, {
controls: false,
...props,
ref,
onPlay: wrapEvent(props.onPlay, onPlay),
onPlaying: wrapEvent(props.onPlaying, onPlaying),
onWaiting: wrapEvent(props.onWaiting, onWaiting),
onPause: wrapEvent(props.onPause, onPause),
onVolumeChange: wrapEvent(props.onVolumeChange, onVolumeChange),
onDurationChange: wrapEvent(props.onDurationChange, onDurationChange),
onTimeUpdate: wrapEvent(props.onTimeUpdate, onTimeUpdate),
onProgress: wrapEvent(props.onProgress, onProgress),
} as any); // TODO: fix this typing.
}
// Some browsers return `Promise` on `.play()` and may throw errors
// if one tries to execute another `.play()` or `.pause()` while that
// promise is resolving. So we prevent that with this lock.
// See: https://bugs.chromium.org/p/chromium/issues/detail?id=593273
let lockPlay: boolean = false;
const controls = {
play: () => {
const el = ref.current;
if (!el) {
return undefined;
}
if (!lockPlay) {
const promise = el.play();
const isPromise = typeof promise === 'object';
if (isPromise) {
lockPlay = true;
const resetLock = () => {
lockPlay = false;
};
promise.then(resetLock, resetLock);
}
return promise;
}
return undefined;
},
pause: () => {
const el = ref.current;
if (el && !lockPlay) {
return el.pause();
}
},
seek: (time: number) => {
const el = ref.current;
if (!el || state.duration === undefined) {
return;
}
time = Math.min(state.duration, Math.max(0, time));
el.currentTime = time;
},
volume: (volume: number) => {
const el = ref.current;
if (!el) {
return;
}
volume = Math.min(1, Math.max(0, volume));
el.volume = volume;
setState({ volume });
},
mute: () => {
const el = ref.current;
if (!el) {
return;
}
el.muted = true;
},
unmute: () => {
const el = ref.current;
if (!el) {
return;
}
el.muted = false;
},
};
useEffect(() => {
const el = ref.current!;
if (!el) {
if (process.env.NODE_ENV !== 'production') {
if (tag === 'audio') {
console.error(
'useAudio() ref to <audio> element is empty at mount. ' +
'It seem you have not rendered the audio element, which it ' +
'returns as the first argument const [audio] = useAudio(...).'
);
} else if (tag === 'video') {
console.error(
'useVideo() ref to <video> element is empty at mount. ' +
'It seem you have not rendered the video element, which it ' +
'returns as the first argument const [video] = useVideo(...).'
);
}
}
return;
}
setState({
volume: el.volume,
muted: el.muted,
paused: el.paused,
});
// Start media, if autoPlay requested.
if (props.autoPlay && el.paused) {
controls.play();
}
}, [props.src]);
return [element, state, controls, ref] as const;
};
}
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
解释
createHTMLMediaHook 是一个高阶函数,用于创建 React 自定义 Hook,简化了对 和 元素的控制。它结合了 React 的状态管理和生命周期钩子来提供一个便捷的接口,用于处理 HTML 媒体元素的播放、暂停、音量控制等操作。以下是对 createHTMLMediaHook 函数的详细解析。
createHTMLMediaHook 函数解析
功能
createHTMLMediaHook 主要用于创建处理 HTML 媒体元素(如 和
)的 Hook。这个 Hook 封装了对媒体元素的常见操作和状态管理,提供了一个统一的接口来操作和控制媒体元素。
参数
tag:
类型: 'audio' | 'video'
说明: 指定要创建的 HTML 媒体元素类型,可以是 'audio' 或 'video'。
返回值
返回一个函数,该函数接受两种可能的参数:
elOrProps:
类型: HTMLMediaProps | React.ReactElement
说明: 可以是媒体元素的属性对象,也可以是包含媒体属性的 React 元素。
返回值是一个元组 [element, state, controls, ref]:
element: 渲染的 React 元素( 或
)。
state: 当前的媒体状态(HTMLMediaState)。
controls: 控制媒体播放的函数(HTMLMediaControls)。
ref: 对应媒体元素的 ref。
实现细节
参数处理
let element: React.ReactElement> | undefined;
let props: MediaPropsWithRef;
if (React.isValidElement(elOrProps)) {
element = elOrProps;
props = element.props;
} else {
props = elOrProps;
}
1
2
3
4
5
6
7
8
9
如果 elOrProps 是一个 React 元素,则提取其属性。
否则,elOrProps 被认为是直接的媒体属性。
状态和引用
const [state, setState] = useSetState({
buffered: [],
time: 0,
duration: 0,
paused: true,
muted: false,
volume: 1,
playing: false,
});
const ref = useRef(null);
1
2
3
4
5
6
7
8
9
10
使用 useSetState 管理媒体状态。有关 useSetState 具体解释可以阅读 WHAT - 通过 react-use 源码学习 React(State 篇)
ref 是一个 useRef,用于引用实际的媒体元素。
事件处理
const wrapEvent = (userEvent, proxyEvent?) => {
return (event) => {
try {
proxyEvent && proxyEvent(event);
} finally {
userEvent && userEvent(event);
}
};
};
1
2
3
4
5
6
7
8
9
wrapEvent 用于将用户提供的事件处理函数和内部事件处理函数组合在一起。
内部事件处理函数(如 onPlay、onPause 等)更新状态以反映媒体元素的当前状态。
创建和渲染媒体元素
if (element) {
element = React.cloneElement(element, {
controls: false,
...props,
ref,
onPlay: wrapEvent(props.onPlay, onPlay),
onPlaying: wrapEvent(props.onPlaying, onPlaying),
onWaiting: wrapEvent(props.onWaiting, onWaiting),
onPause: wrapEvent(props.onPause, onPause),
onVolumeChange: wrapEvent(props.onVolumeChange, onVolumeChange),
onDurationChange: wrapEvent(props.onDurationChange, onDurationChange),
onTimeUpdate: wrapEvent(props.onTimeUpdate, onTimeUpdate),
onProgress: wrapEvent(props.onProgress, onProgress),
});
} else {
element = React.createElement(tag, {
controls: false,
...props,
ref,
onPlay: wrapEvent(props.onPlay, onPlay),
onPlaying: wrapEvent(props.onPlaying, onPlaying),
onWaiting: wrapEvent(props.onWaiting, onWaiting),
onPause: wrapEvent(props.onPause, onPause),
onVolumeChange: wrapEvent(props.onVolumeChange, onVolumeChange),
onDurationChange: wrapEvent(props.onDurationChange, onDurationChange),
onTimeUpdate: wrapEvent(props.onTimeUpdate, onTimeUpdate),
onProgress: wrapEvent(props.onProgress, onProgress),
} as any); // TODO: fix this typing.
}
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
如果提供了元素,则克隆并扩展其属性。
如果没有提供元素,则创建新的媒体元素。
控制方法
const controls = {
play: () => { / ... / },
pause: () => { / ... / },
seek: (time: number) => { / ... / },
volume: (volume: number) => { / ... / },
mute: () => { / ... / },
unmute: () => { / ... / },
};
1
2
3
4
5
6
7
8
controls 对象提供了对媒体元素进行播放、暂停、音量调整等操作的方法。
play 和 pause 方法处理 Promise,确保不会重复调用 play 方法。
seek 方法调整播放时间。
volume、mute 和 unmute 方法调整音量和静音状态。
副作用
useEffect(() => {
const el = ref.current!;
if (!el) {
// Handle error
return;
}
setState({
volume: el.volume,
muted: el.muted,
paused: el.paused,
});
if (props.autoPlay && el.paused) {
controls.play();
}
}, [props.src]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
在组件挂载时,设置初始状态并根据 props.autoPlay 自动播放媒体。
总结
createHTMLMediaHook: 用于创建一个自定义 Hook 来处理 或
元素,封装了媒体元素的控制和状态管理。
事件处理: 内部事件处理函数更新 Hook 状态,以反映媒体元素的当前状态。
controls: 提供了用于播放、暂停、调整音量等功能的方法。
副作用: 通过 useEffect 确保在媒体源更改时更新状态,并根据 autoPlay 属性自动播放。