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

news/2024/9/14 13:16:48 标签: react.js, 学习, ui

目录

  • 一、官方介绍
    • 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 HTMLMediaProps
  extends React.AudioHTMLAttributes<uiltin">any>,
    React.VideoHTMLAttributes<uiltin">any> {
  src: uiltin">string;
}

export interface HTMLMediaState {
  buffered: uiltin">any[];
  duration: uiltin">number;
  paused: uiltin">boolean;
  muted: uiltin">boolean;
  time: uiltin">number;
  volume: uiltin">number;
  playing: uiltin">boolean;
}

export interface HTMLMediaControls {
  play: () => uiltin">Promise<void> | void;
  pause: () => void;
  mute: () => void;
  unmute: () => void;
  volume: (volume: uiltin">number) => void;
  seek: (time: uiltin">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 uiltin">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: uiltin">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: uiltin">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: uiltin">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') {
            uiltin">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') {
            uiltin">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

实现细节

  1. 参数处理

    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 被认为是直接的媒体属性。
  2. 状态和引用

    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,用于引用实际的媒体元素。
  3. 事件处理

    const wrapEvent = (userEvent, proxyEvent?) => {
      return (event) => {
        try {
          proxyEvent && proxyEvent(event);
        } finally {
          userEvent && userEvent(event);
        }
      };
    };
    
    • wrapEvent 用于将用户提供的事件处理函数和内部事件处理函数组合在一起。

    • 内部事件处理函数(如 onPlayonPause 等)更新状态以反映媒体元素的当前状态。

  4. 创建和渲染媒体元素

    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 uiltin">any); // TODO: fix this typing.
    }
    
    • 如果提供了元素,则克隆并扩展其属性。
    • 如果没有提供元素,则创建新的媒体元素。
  5. 控制方法

    const controls = {
      play: () => { /* ... */ },
      pause: () => { /* ... */ },
      seek: (time: uiltin">number) => { /* ... */ },
      volume: (volume: uiltin">number) => { /* ... */ },
      mute: () => { /* ... */ },
      unmute: () => { /* ... */ },
    };
    
    • controls 对象提供了对媒体元素进行播放、暂停、音量调整等操作的方法。
    • playpause 方法处理 Promise,确保不会重复调用 play 方法。
    • seek 方法调整播放时间。
    • volumemuteunmute 方法调整音量和静音状态。
  6. 副作用

    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]);
    
    • 在组件挂载时,设置初始状态并根据 props.autoPlay 自动播放媒体。

总结

  • createHTMLMediaHook: 用于创建一个自定义 Hook 来处理 <audio><video> 元素,封装了媒体元素的控制和状态管理。
  • 事件处理: 内部事件处理函数更新 Hook 状态,以反映媒体元素的当前状态。
  • controls: 提供了用于播放、暂停、调整音量等功能的方法。
  • 副作用: 通过 useEffect 确保在媒体源更改时更新状态,并根据 autoPlay 属性自动播放。

这个 Hook 提供了一个简洁的 API 来处理媒体元素的常见操作,使得在 React 组件中操作音视频元素变得更加方便和一致。


http://www.niftyadmin.cn/n/5628671.html

相关文章

​拓​竹​二​面​

sex:male 45min 1. 假设你是一名前端开发人员&#xff0c;请尽可能详细地说明&#xff0c;React和Vue在开发感受上的对比。你的回答中不要写出示例代码。 作为一名前端开发人员&#xff0c;React和Vue都是非常流行的JavaScript框架&#xff0c;它们各自有着不同的设计理念和特…

echarts组件——漏斗图

echarts组件——漏斗图 组件代码 <template><div :class"classname" :style"{height:height,width:width}" /> </template><script> import * as echarts from echarts require(echarts/theme/macarons) // echarts theme import…

【AI大事记】——你值得拥有的AI小辞典(第六期)

上期获奖人员&#xff1a; 社区昵称 奖品 冬冬 蚂蚁周边大容量托特包&#xff08;一等奖&#xff09; Ghana 蚂蚁周边真丝眼罩&#xff08;二等奖&#xff09; 学编程的小程 3000社区积分 大猫 3000社区积分 user_1723692187462 1500社区积分 嘿嘿 1500社区积分 …

uni-app布局

一. 如何让元素吸顶? position: sticky;top: 0; 注意&#xff1a;暂时仅支持作为list-view、sticky-section的子节点, sticky-header不支持css样式&#xff01;当一个容器视图设置多个sticky-header时&#xff0c;后一个sticky-header会停靠在前一个sticky-header的末尾处。

K8S StatefulSet

Kubernetes StatefulSet 是 Kubernetes 中的一个核心概念,用于管理有状态应用的 Pod 部署和伸缩。与无状态应用相比,有状态应用通常需要持久化存储、唯一标识和有序部署等特性,而 StatefulSet 正是为了满足这些需求而设计的。 一、StatefulSet 的特点 稳定的唯一网络标识符…

ubuntu桌面版安装后无ssh

1. 问题 内网无网络&#xff0c;安装kvm虚拟机&#xff0c;镜像为ubuntu-22.04.3-desktop-amd64.iso&#xff1a; 安装后配置ip地址&#xff0c;ssh连接失败&#xff0c;查看ssh服务不存在&#xff1b;配置本地镜像源&#xff0c;apt-cache search openssh没有openssh-server…

六西格玛绿带可以主导黑带项目吗?

在深入探讨“六西格玛绿带是否可以主导黑带项目”这一话题时&#xff0c;我们首先需要明确六西格玛管理体系中绿带与黑带各自的角色、职责及其在项目推进中的定位。六西格玛作为一种旨在通过减少缺陷和变异&#xff0c;提高流程效率和盈利能力的质量管理方法&#xff0c;其成功…

Python + Playwright(24):处理HTTPS错误

Python + Playwright(24):处理HTTPS错误 前言一、什么是HTTPS错误?二、`ignore_https_errors=True` 的作用三、使用场景总结前言 当浏览器试图访问一个网站时,如果该网站的SSL证书无效或未被信任,浏览器通常会阻止访问,并显示一条安全警告。这种情况下,如何绕过这些错误…