目录
- 一、官方介绍
- 1. Sensors
- 2. UI
- 3. Animations
- 4. Side-Effects
- 5. Lifecycles
- 6. State
- 7. Miscellaneous
- 二、源码学习
- 示例:n. xx - yy
- UI - useAudio
- `createHTMLMediaHook` 函数解析
- 功能
- 参数
- **返回值**
- **实现细节**
- **总结**
一、官方介绍
Github 地址
react-use 是一个流行的 React 自定义 Hook 库,提供了一组常用的 Hook,以帮助开发者在 React 应用程序中更方便地处理常见的任务和功能。
官方将 react-use
的 Hook 分成了以下几个主要类别,以便更好地组织和查找常用的功能。每个类别涵盖了不同类型的 Hook,满足各种开发需求。以下是这些类别的详细说明:
1. Sensors
- 功能: 主要涉及与浏览器或用户交互相关的传感器功能。
- 示例:
useMouse
: 获取鼠标位置。useWindowSize
: 获取窗口尺寸。useBattery
: 监控电池状态。
2. UI
- 功能: 涉及用户界面相关的功能,如处理样式、显示和隐藏元素等。
- 示例:
useClickAway
: 监听点击事件以检测用户点击是否发生在组件外部。useMeasure
: 测量元素的大小和位置。useDarkMode
: 管理和检测暗模式状态。
3. Animations
- 功能: 处理动画和过渡效果。
- 示例:
useSpring
: 使用react-spring
处理动画效果。useTransition
: 使用react-spring
处理过渡动画。
4. Side-Effects
- 功能: 处理副作用相关的 Hook,包括数据获取、异步操作等。
- 示例:
useAsync
: 处理异步操作,如数据获取,并提供状态和结果。useFetch
: 简化数据获取操作。useAxios
: 使用 Axios 进行数据请求的 Hook。
5. Lifecycles
- 功能: 处理组件生命周期相关的 Hook。
- 示例:
useMount
: 在组件挂载时执行的 Hook。useUnmount
: 在组件卸载时执行的 Hook。useUpdate
: 在组件更新时执行的 Hook。
6. State
- 功能: 管理组件状态和相关逻辑。
- 示例:
useState
: 提供基本状态管理功能。useReducer
: 替代useState
实现更复杂的状态逻辑。useForm
: 管理表单状态和验证。useInput
: 管理输入字段的状态。
7. 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 (<div>{audio}<pre>{JSON.stringify(state, null, 2)}</pre><button onClick={controls.pause}>Pause</button><button onClick={controls.play}>Play</button><br/><button onClick={controls.mute}>Mute</button><button onClick={controls.unmute}>Un-mute</button><br/><button onClick={() => controls.volume(.1)}>Volume: 10%</button><button onClick={() => controls.volume(.5)}>Volume: 50%</button><button onClick={() => controls.volume(1)}>Volume: 100%</button><br/><button onClick={() => controls.seek(state.time - 5)}>-5 sec</button><button onClick={() => controls.seek(state.time + 5)}>+5 sec</button></div>);
};
源码
import createHTMLMediaHook from './factory/createHTMLMediaHook';const useAudio = createHTMLMediaHook<HTMLAudioElement>('audio');
export default useAudio;
//./factory/createHTMLMediaHook
import * as React from 'react';
import { useEffect, useRef } from 'react';
import useSetState from '../useSetState';
import parseTimeRanges from '../misc/parseTimeRanges';export interface HTMLMediaPropsextends React.AudioHTMLAttributes<any>,React.VideoHTMLAttributes<any> {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> | void;pause: () => void;mute: () => void;unmute: () => void;volume: (volume: number) => void;seek: (time: number) => void;
}type MediaPropsWithRef<T> = HTMLMediaProps & { ref?: React.MutableRefObject<T | null> };export default function createHTMLMediaHook<T extends HTMLAudioElement | HTMLVideoElement>(tag: 'audio' | 'video'
) {return (elOrProps: HTMLMediaProps | React.ReactElement<HTMLMediaProps>) => {let element: React.ReactElement<MediaPropsWithRef<T>> | undefined;let props: MediaPropsWithRef<T>;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=593273let 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;};
}
解释
createHTMLMediaHook
是一个高阶函数,用于创建 React 自定义 Hook,简化了对 <audio>
和 <video>
元素的控制。它结合了 React 的状态管理和生命周期钩子来提供一个便捷的接口,用于处理 HTML 媒体元素的播放、暂停、音量控制等操作。以下是对 createHTMLMediaHook
函数的详细解析。
createHTMLMediaHook
函数解析
功能
createHTMLMediaHook
主要用于创建处理 HTML 媒体元素(如 <audio>
和 <video>
)的 Hook。这个 Hook 封装了对媒体元素的常见操作和状态管理,提供了一个统一的接口来操作和控制媒体元素。
参数
tag
:- 类型:
'audio' | 'video'
- 说明: 指定要创建的 HTML 媒体元素类型,可以是
'audio'
或'video'
。
- 类型:
返回值
- 返回一个函数,该函数接受两种可能的参数:
elOrProps
:- 类型:
HTMLMediaProps | React.ReactElement<HTMLMediaProps>
- 说明: 可以是媒体元素的属性对象,也可以是包含媒体属性的 React 元素。
- 类型:
- 返回值是一个元组
[element, state, controls, ref]
:element
: 渲染的 React 元素(<audio>
或<video>
)。state
: 当前的媒体状态(HTMLMediaState
)。controls
: 控制媒体播放的函数(HTMLMediaControls
)。ref
: 对应媒体元素的ref
。
实现细节
-
参数处理
let element: React.ReactElement<MediaPropsWithRef<T>> | undefined; let props: MediaPropsWithRef<T>;if (React.isValidElement(elOrProps)) {element = elOrProps;props = element.props; } else {props = elOrProps; }
- 如果
elOrProps
是一个 React 元素,则提取其属性。 - 否则,
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);
- 使用
useSetState
管理媒体状态。有关useSetState
具体解释可以阅读 WHAT - 通过 react-use 源码学习 React(State 篇) ref
是一个useRef
,用于引用实际的媒体元素。
- 使用
-
事件处理
const wrapEvent = (userEvent, proxyEvent?) => {return (event) => {try {proxyEvent && proxyEvent(event);} finally {userEvent && userEvent(event);}}; };
-
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. }
- 如果提供了元素,则克隆并扩展其属性。
- 如果没有提供元素,则创建新的媒体元素。
-
控制方法
const controls = {play: () => { /* ... */ },pause: () => { /* ... */ },seek: (time: number) => { /* ... */ },volume: (volume: number) => { /* ... */ },mute: () => { /* ... */ },unmute: () => { /* ... */ }, };
controls
对象提供了对媒体元素进行播放、暂停、音量调整等操作的方法。play
和pause
方法处理Promise
,确保不会重复调用play
方法。seek
方法调整播放时间。volume
、mute
和unmute
方法调整音量和静音状态。
-
副作用
useEffect(() => {const el = ref.current!;if (!el) {// Handle errorreturn;}setState({volume: el.volume,muted: el.muted,paused: el.paused,});if (props.autoPlay && el.paused) {controls.play();} }, [props.src]);
- 在组件挂载时,设置初始状态并根据
props.autoPlay
自动播放媒体。
- 在组件挂载时,设置初始状态并根据
总结
createHTMLMediaHook
: 用于创建一个自定义 Hook 来处理<audio>
或<video>
元素,封装了媒体元素的控制和状态管理。- 事件处理: 内部事件处理函数更新 Hook 状态,以反映媒体元素的当前状态。
controls
: 提供了用于播放、暂停、调整音量等功能的方法。- 副作用: 通过
useEffect
确保在媒体源更改时更新状态,并根据autoPlay
属性自动播放。
这个 Hook 提供了一个简洁的 API 来处理媒体元素的常见操作,使得在 React 组件中操作音视频元素变得更加方便和一致。