WHAT - 通过 react-use 源码学习 React(UI 篇)

打印 上一主题 下一主题

主题 988|帖子 988|积分 2966

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
一、官方介绍

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.
  利用
  1. import {useAudio} from 'react-use';
  2. const Demo = () => {
  3.   const [audio, state, controls, ref] = useAudio({
  4.     src: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3',
  5.     autoPlay: true,
  6.   });
  7.   return (
  8.     <div>
  9.       {audio}
  10.       <pre>{JSON.stringify(state, null, 2)}</pre>
  11.       <button onClick={controls.pause}>Pause</button>
  12.       <button onClick={controls.play}>Play</button>
  13.       <br/>
  14.       <button onClick={controls.mute}>Mute</button>
  15.       <button onClick={controls.unmute}>Un-mute</button>
  16.       <br/>
  17.       <button onClick={() => controls.volume(.1)}>Volume: 10%</button>
  18.       <button onClick={() => controls.volume(.5)}>Volume: 50%</button>
  19.       <button onClick={() => controls.volume(1)}>Volume: 100%</button>
  20.       <br/>
  21.       <button onClick={() => controls.seek(state.time - 5)}>-5 sec</button>
  22.       <button onClick={() => controls.seek(state.time + 5)}>+5 sec</button>
  23.     </div>
  24.   );
  25. };
复制代码
源码
  1. import createHTMLMediaHook from './factory/createHTMLMediaHook';
  2. const useAudio = createHTMLMediaHook<HTMLAudioElement>('audio');
  3. export default useAudio;
复制代码
  1. //./factory/createHTMLMediaHook
  2. import * as React from 'react';
  3. import { useEffect, useRef } from 'react';
  4. import useSetState from '../useSetState';
  5. import parseTimeRanges from '../misc/parseTimeRanges';
  6. export interface HTMLMediaProps
  7.   extends React.AudioHTMLAttributes<any>,
  8.     React.VideoHTMLAttributes<any> {
  9.   src: string;
  10. }
  11. export interface HTMLMediaState {
  12.   buffered: any[];
  13.   duration: number;
  14.   paused: boolean;
  15.   muted: boolean;
  16.   time: number;
  17.   volume: number;
  18.   playing: boolean;
  19. }
  20. export interface HTMLMediaControls {
  21.   play: () => Promise<void> | void;
  22.   pause: () => void;
  23.   mute: () => void;
  24.   unmute: () => void;
  25.   volume: (volume: number) => void;
  26.   seek: (time: number) => void;
  27. }
  28. type MediaPropsWithRef<T> = HTMLMediaProps & { ref?: React.MutableRefObject<T | null> };
  29. export default function createHTMLMediaHook<T extends HTMLAudioElement | HTMLVideoElement>(
  30.   tag: 'audio' | 'video'
  31. ) {
  32.   return (elOrProps: HTMLMediaProps | React.ReactElement<HTMLMediaProps>) => {
  33.     let element: React.ReactElement<MediaPropsWithRef<T>> | undefined;
  34.     let props: MediaPropsWithRef<T>;
  35.     if (React.isValidElement(elOrProps)) {
  36.       element = elOrProps;
  37.       props = element.props;
  38.     } else {
  39.       props = elOrProps;
  40.     }
  41.     const [state, setState] = useSetState<HTMLMediaState>({
  42.       buffered: [],
  43.       time: 0,
  44.       duration: 0,
  45.       paused: true,
  46.       muted: false,
  47.       volume: 1,
  48.       playing: false,
  49.     });
  50.     const ref = useRef<T | null>(null);
  51.     const wrapEvent = (userEvent, proxyEvent?) => {
  52.       return (event) => {
  53.         try {
  54.           proxyEvent && proxyEvent(event);
  55.         } finally {
  56.           userEvent && userEvent(event);
  57.         }
  58.       };
  59.     };
  60.     const onPlay = () => setState({ paused: false });
  61.     const onPlaying = () => setState({ playing: true });
  62.     const onWaiting = () => setState({ playing: false });
  63.     const onPause = () => setState({ paused: true, playing: false });
  64.     const onVolumeChange = () => {
  65.       const el = ref.current;
  66.       if (!el) {
  67.         return;
  68.       }
  69.       setState({
  70.         muted: el.muted,
  71.         volume: el.volume,
  72.       });
  73.     };
  74.     const onDurationChange = () => {
  75.       const el = ref.current;
  76.       if (!el) {
  77.         return;
  78.       }
  79.       const { duration, buffered } = el;
  80.       setState({
  81.         duration,
  82.         buffered: parseTimeRanges(buffered),
  83.       });
  84.     };
  85.     const onTimeUpdate = () => {
  86.       const el = ref.current;
  87.       if (!el) {
  88.         return;
  89.       }
  90.       setState({ time: el.currentTime });
  91.     };
  92.     const onProgress = () => {
  93.       const el = ref.current;
  94.       if (!el) {
  95.         return;
  96.       }
  97.       setState({ buffered: parseTimeRanges(el.buffered) });
  98.     };
  99.     if (element) {
  100.       element = React.cloneElement(element, {
  101.         controls: false,
  102.         ...props,
  103.         ref,
  104.         onPlay: wrapEvent(props.onPlay, onPlay),
  105.         onPlaying: wrapEvent(props.onPlaying, onPlaying),
  106.         onWaiting: wrapEvent(props.onWaiting, onWaiting),
  107.         onPause: wrapEvent(props.onPause, onPause),
  108.         onVolumeChange: wrapEvent(props.onVolumeChange, onVolumeChange),
  109.         onDurationChange: wrapEvent(props.onDurationChange, onDurationChange),
  110.         onTimeUpdate: wrapEvent(props.onTimeUpdate, onTimeUpdate),
  111.         onProgress: wrapEvent(props.onProgress, onProgress),
  112.       });
  113.     } else {
  114.       element = React.createElement(tag, {
  115.         controls: false,
  116.         ...props,
  117.         ref,
  118.         onPlay: wrapEvent(props.onPlay, onPlay),
  119.         onPlaying: wrapEvent(props.onPlaying, onPlaying),
  120.         onWaiting: wrapEvent(props.onWaiting, onWaiting),
  121.         onPause: wrapEvent(props.onPause, onPause),
  122.         onVolumeChange: wrapEvent(props.onVolumeChange, onVolumeChange),
  123.         onDurationChange: wrapEvent(props.onDurationChange, onDurationChange),
  124.         onTimeUpdate: wrapEvent(props.onTimeUpdate, onTimeUpdate),
  125.         onProgress: wrapEvent(props.onProgress, onProgress),
  126.       } as any); // TODO: fix this typing.
  127.     }
  128.     // Some browsers return `Promise` on `.play()` and may throw errors
  129.     // if one tries to execute another `.play()` or `.pause()` while that
  130.     // promise is resolving. So we prevent that with this lock.
  131.     // See: https://bugs.chromium.org/p/chromium/issues/detail?id=593273
  132.     let lockPlay: boolean = false;
  133.     const controls = {
  134.       play: () => {
  135.         const el = ref.current;
  136.         if (!el) {
  137.           return undefined;
  138.         }
  139.         if (!lockPlay) {
  140.           const promise = el.play();
  141.           const isPromise = typeof promise === 'object';
  142.           if (isPromise) {
  143.             lockPlay = true;
  144.             const resetLock = () => {
  145.               lockPlay = false;
  146.             };
  147.             promise.then(resetLock, resetLock);
  148.           }
  149.           return promise;
  150.         }
  151.         return undefined;
  152.       },
  153.       pause: () => {
  154.         const el = ref.current;
  155.         if (el && !lockPlay) {
  156.           return el.pause();
  157.         }
  158.       },
  159.       seek: (time: number) => {
  160.         const el = ref.current;
  161.         if (!el || state.duration === undefined) {
  162.           return;
  163.         }
  164.         time = Math.min(state.duration, Math.max(0, time));
  165.         el.currentTime = time;
  166.       },
  167.       volume: (volume: number) => {
  168.         const el = ref.current;
  169.         if (!el) {
  170.           return;
  171.         }
  172.         volume = Math.min(1, Math.max(0, volume));
  173.         el.volume = volume;
  174.         setState({ volume });
  175.       },
  176.       mute: () => {
  177.         const el = ref.current;
  178.         if (!el) {
  179.           return;
  180.         }
  181.         el.muted = true;
  182.       },
  183.       unmute: () => {
  184.         const el = ref.current;
  185.         if (!el) {
  186.           return;
  187.         }
  188.         el.muted = false;
  189.       },
  190.     };
  191.     useEffect(() => {
  192.       const el = ref.current!;
  193.       if (!el) {
  194.         if (process.env.NODE_ENV !== 'production') {
  195.           if (tag === 'audio') {
  196.             console.error(
  197.               'useAudio() ref to <audio> element is empty at mount. ' +
  198.                 'It seem you have not rendered the audio element, which it ' +
  199.                 'returns as the first argument const [audio] = useAudio(...).'
  200.             );
  201.           } else if (tag === 'video') {
  202.             console.error(
  203.               'useVideo() ref to <video> element is empty at mount. ' +
  204.                 'It seem you have not rendered the video element, which it ' +
  205.                 'returns as the first argument const [video] = useVideo(...).'
  206.             );
  207.           }
  208.         }
  209.         return;
  210.       }
  211.       setState({
  212.         volume: el.volume,
  213.         muted: el.muted,
  214.         paused: el.paused,
  215.       });
  216.       // Start media, if autoPlay requested.
  217.       if (props.autoPlay && el.paused) {
  218.         controls.play();
  219.       }
  220.     }, [props.src]);
  221.     return [element, state, controls, ref] as const;
  222.   };
  223. }
复制代码
解释
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。


实现细节


  • 参数处置惩罚
    1. let element: React.ReactElement<MediaPropsWithRef<T>> | undefined;
    2. let props: MediaPropsWithRef<T>;
    3. if (React.isValidElement(elOrProps)) {
    4.   element = elOrProps;
    5.   props = element.props;
    6. } else {
    7.   props = elOrProps;
    8. }
    复制代码

    • 假如 elOrProps 是一个 React 元素,则提取其属性。
    • 否则,elOrProps 被认为是直接的媒体属性。

  • 状态和引用
    1. const [state, setState] = useSetState<HTMLMediaState>({
    2.   buffered: [],
    3.   time: 0,
    4.   duration: 0,
    5.   paused: true,
    6.   muted: false,
    7.   volume: 1,
    8.   playing: false,
    9. });
    10. const ref = useRef<T | null>(null);
    复制代码

    • 利用 useSetState 管理媒体状态。有关 useSetState 详细解释可以阅读 WHAT - 通过 react-use 源码学习 React(State 篇)
    • ref 是一个 useRef,用于引用实际的媒体元素。

  • 变乱处置惩罚
    1. const wrapEvent = (userEvent, proxyEvent?) => {
    2.   return (event) => {
    3.     try {
    4.       proxyEvent && proxyEvent(event);
    5.     } finally {
    6.       userEvent && userEvent(event);
    7.     }
    8.   };
    9. };
    复制代码

    • wrapEvent 用于将用户提供的变乱处置惩罚函数和内部变乱处置惩罚函数组合在一起。
    • 内部变乱处置惩罚函数(如 onPlay、onPause 等)更新状态以反映媒体元素的当前状态。

  • 创建和渲染媒体元素
    1. if (element) {
    2.   element = React.cloneElement(element, {
    3.     controls: false,
    4.     ...props,
    5.     ref,
    6.     onPlay: wrapEvent(props.onPlay, onPlay),
    7.     onPlaying: wrapEvent(props.onPlaying, onPlaying),
    8.     onWaiting: wrapEvent(props.onWaiting, onWaiting),
    9.     onPause: wrapEvent(props.onPause, onPause),
    10.     onVolumeChange: wrapEvent(props.onVolumeChange, onVolumeChange),
    11.     onDurationChange: wrapEvent(props.onDurationChange, onDurationChange),
    12.     onTimeUpdate: wrapEvent(props.onTimeUpdate, onTimeUpdate),
    13.     onProgress: wrapEvent(props.onProgress, onProgress),
    14.   });
    15. } else {
    16.   element = React.createElement(tag, {
    17.     controls: false,
    18.     ...props,
    19.     ref,
    20.     onPlay: wrapEvent(props.onPlay, onPlay),
    21.     onPlaying: wrapEvent(props.onPlaying, onPlaying),
    22.     onWaiting: wrapEvent(props.onWaiting, onWaiting),
    23.     onPause: wrapEvent(props.onPause, onPause),
    24.     onVolumeChange: wrapEvent(props.onVolumeChange, onVolumeChange),
    25.     onDurationChange: wrapEvent(props.onDurationChange, onDurationChange),
    26.     onTimeUpdate: wrapEvent(props.onTimeUpdate, onTimeUpdate),
    27.     onProgress: wrapEvent(props.onProgress, onProgress),
    28.   } as any); // TODO: fix this typing.
    29. }
    复制代码

    • 假如提供了元素,则克隆并扩展其属性。
    • 假如没有提供元素,则创建新的媒体元素。

  • 控制方法
    1. const controls = {
    2.   play: () => { /* ... */ },
    3.   pause: () => { /* ... */ },
    4.   seek: (time: number) => { /* ... */ },
    5.   volume: (volume: number) => { /* ... */ },
    6.   mute: () => { /* ... */ },
    7.   unmute: () => { /* ... */ },
    8. };
    复制代码

    • controls 对象提供了对媒体元素进行播放、停息、音量调整等操作的方法。
    • play 和 pause 方法处置惩罚 Promise,确保不会重复调用 play 方法。
    • seek 方法调整播放时间。
    • volume、mute 和 unmute 方法调整音量和静音状态。

  • 副作用
    1. useEffect(() => {
    2.   const el = ref.current!;
    3.   if (!el) {
    4.     // Handle error
    5.     return;
    6.   }
    7.   setState({
    8.     volume: el.volume,
    9.     muted: el.muted,
    10.     paused: el.paused,
    11.   });
    12.   if (props.autoPlay && el.paused) {
    13.     controls.play();
    14.   }
    15. }, [props.src]);
    复制代码

    • 在组件挂载时,设置初始状态并根据 props.autoPlay 主动播放媒体。

总结



  • createHTMLMediaHook: 用于创建一个自定义 Hook 来处置惩罚 <audio> 或 <video> 元素,封装了媒体元素的控制和状态管理。
  • 变乱处置惩罚: 内部变乱处置惩罚函数更新 Hook 状态,以反映媒体元素的当前状态。
  • controls: 提供了用于播放、停息、调整音量等功能的方法。
  • 副作用: 通过 useEffect 确保在媒体源更改时更新状态,并根据 autoPlay 属性主动播放。
这个 Hook 提供了一个简洁的 API 来处置惩罚媒体元素的常见操作,使得在 React 组件中操作音视频元素变得更加方便和同等。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

祗疼妳一个

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表