WHAT - 通过 react-use 源码学习 React

一、官方介绍

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 更加有组织和易于查找,帮助开发者快速找到需要的功能并有效地集成到他们的应用程序中。

二、源码学习

1. Lifecycles - useEffectOnce

a modified useEffect hook that only runs once.

使用

import {useEffectOnce} from 'react-use';

const Demo = () => {
  useEffectOnce(() => {
    console.log('Running effect once on mount')

    return () => {
      console.log('Running clean-up of effect on unmount')
    }
  });

  return null;
};

源码

import { EffectCallback, useEffect } from 'react';
const useEffectOnce = (effect: EffectCallback) => {
  useEffect(effect, []);
};
export default useEffectOnce;

解释

import { EffectCallback, useEffect } from 'react';
  • EffectCallback:这是 TypeScript 中的一个类型,表示传递给 useEffect 的副作用函数的类型。它是一个回调函数,通常用于定义副作用。
  • useEffect:这是 React 的一个 Hook,用于处理副作用(side effects),例如数据获取、订阅、DOM 操作等。
const useEffectOnce = (effect: EffectCallback) => {
  useEffect(effect, []);
};
  • useEffectOnce:这是一个自定义 Hook,接收一个副作用函数 effect 作为参数。自定义 Hook 是函数,它可以调用其他 Hook 并返回值。

  • useEffect(effect, []):调用了 React 的 useEffect Hook。useEffect 的第一个参数是副作用函数 effect,第二个参数是依赖数组(dependencies array)。

    • 副作用函数 effect:当组件挂载时执行,并在组件更新或卸载时执行清理(如果副作用函数返回一个清理函数)。

    • 依赖数组 []:这个数组定义了副作用函数的依赖项。空数组 [] 表示副作用函数只在组件挂载时执行一次。这是因为 useEffect 只有在依赖数组中的值发生变化时才会重新执行副作用函数,但由于这里依赖数组为空,副作用函数仅在组件初次渲染时执行一次。

2. Lifecycles - useEvent

subscribe to events.

使用

import {useEvent, useList} from 'react-use';

const Demo = () => {
  const [list, {push, clear}] = useList();

  const onKeyDown = useCallback(({key}) => {
    if (key === 'r') clear();
    push(key);
  }, []);

  useEvent('keydown', onKeyDown);

  return (
    <div>
      <p>
        Press some keys on your keyboard, <code style={{color: 'tomato'}}>r</code> key resets the list
      </p>
      <pre>
        {JSON.stringify(list, null, 4)}
      </pre>
    </div>
  );
};

源码

import { useEffect } from 'react';
import { isBrowser, off, on } from './misc/util';

export interface ListenerType1 {
  addEventListener(name: string, handler: (event?: any) => void, ...args: any[]);

  removeEventListener(name: string, handler: (event?: any) => void, ...args: any[]);
}

export interface ListenerType2 {
  on(name: string, handler: (event?: any) => void, ...args: any[]);

  off(name: string, handler: (event?: any) => void, ...args: any[]);
}

export type UseEventTarget = ListenerType1 | ListenerType2;

const defaultTarget = isBrowser ? window : null;

const isListenerType1 = (target: any): target is ListenerType1 => {
  return !!target.addEventListener;
};
const isListenerType2 = (target: any): target is ListenerType2 => {
  return !!target.on;
};

type AddEventListener<T> = T extends ListenerType1
  ? T['addEventListener']
  : T extends ListenerType2
  ? T['on']
  : never;

export type UseEventOptions<T> = Parameters<AddEventListener<T>>[2];

const useEvent = <T extends UseEventTarget>(
  name: Parameters<AddEventListener<T>>[0],
  handler?: null | undefined | Parameters<AddEventListener<T>>[1],
  target: null | T | Window = defaultTarget,
  options?: UseEventOptions<T>
) => {
  useEffect(() => {
    if (!handler) {
      return;
    }
    if (!target) {
      return;
    }
    if (isListenerType1(target)) {
      on(target, name, handler, options);
    } else if (isListenerType2(target)) {
      target.on(name, handler, options);
    }
    return () => {
      if (isListenerType1(target)) {
        off(target, name, handler, options);
      } else if (isListenerType2(target)) {
        target.off(name, handler, options);
      }
    };
  }, [name, handler, target, JSON.stringify(options)]);
};

export default useEvent;

解释

这个 Hook 旨在帮助处理事件监听的操作,它支持两种不同的事件监听接口,并且使用 TypeScript 进行类型检查。

import { useEffect } from 'react';
import { isBrowser, off, on } from './misc/util';
  • useEffect: 用于处理副作用。
  • isBrowser: 可能是一个布尔值,用于检查是否在浏览器环境中。具体实现可以阅读 misc/util.ts
  • offon: 这些可能是自定义的工具函数,用于添加和移除事件监听器。具体实现可以阅读 misc/util.ts

这里贴出来 on 的具体实现:

export function on<T extends Window | Document | HTMLElement | EventTarget>(
  obj: T | null,
  ...args: Parameters<T['addEventListener']> | [string, Function | null, ...any]
): void {
  if (obj && obj.addEventListener) {
    obj.addEventListener(...(args as Parameters<HTMLElement['addEventListener']>));
  }
}

其中类型 [string, Function | null, ...any] 是一个元组类型(Tuple Type)定义,它描述了一个具有特定结构的数组。元组(Tuple) 是 TypeScript 中的一个数据结构,用于表示固定长度和已知元素类型的数组。每个元素的类型可以不同,并且可以访问每个元素。...any 使用了 TypeScript 的 Rest Parameters,表示在元组的前两个元素之后,可以有零个或多个任意类型的元素。any 类型意味着这些元素可以是任何类型(number、string、boolean、object 等)。

接着看源码:

export interface ListenerType1 {
  addEventListener(name: string, handler: (event?: any) => void, ...args: any[]);
  removeEventListener(name: string, handler: (event?: any) => void, ...args: any[]);
}

export interface ListenerType2 {
  on(name: string, handler: (event?: any) => void, ...args: any[]);
  off(name: string, handler: (event?: any) => void, ...args: any[]);
}
  • ListenerType1ListenerType2: 定义了两种不同的事件监听接口。
    • ListenerType1 使用 addEventListenerremoveEventListener 方法。
    • ListenerType2 使用 onoff 方法。
export type UseEventTarget = ListenerType1 | ListenerType2;
  • UseEventTarget: 联合类型,表示可以是 ListenerType1ListenerType2 中的任何一个。
const defaultTarget = isBrowser ? window : null;
  • defaultTarget: 根据 isBrowser 判断是否在浏览器环境中,如果是则默认为 window,否则为 null
const isListenerType1 = (target: any): target is ListenerType1 => {
  return !!target.addEventListener;
};

const isListenerType2 = (target: any): target is ListenerType2 => {
  return !!target.on;
};
  • isListenerType1isListenerType2: 类型谓词函数,用于检查 target 是否符合 ListenerType1ListenerType2 类型。
type AddEventListener<T> = T extends ListenerType1
  ? T['addEventListener']
  : T extends ListenerType2
  ? T['on']
  : never;

export type UseEventOptions<T> = Parameters<AddEventListener<T>>[2];
  • AddEventListener: 条件类型,根据 T 的类型决定是 addEventListener 还是 on 方法。
  • UseEventOptions: 提取 AddEventListener 的第三个参数类型,这通常是事件监听选项(如 capture)。
const useEvent = <T extends UseEventTarget>(
  name: Parameters<AddEventListener<T>>[0],
  handler?: null | undefined | Parameters<AddEventListener<T>>[1],
  target: null | T | Window = defaultTarget,
  options?: UseEventOptions<T>
) => {
  useEffect(() => {
    if (!handler) {
      return;
    }
    if (!target) {
      return;
    }
    if (isListenerType1(target)) {
      on(target, name, handler, options);
    } else if (isListenerType2(target)) {
      target.on(name, handler, options);
    }
    return () => {
      if (isListenerType1(target)) {
        off(target, name, handler, options);
      } else if (isListenerType2(target)) {
        target.off(name, handler, options);
      }
    };
  }, [name, handler, target, JSON.stringify(options)]);
};
  • 参数:

    • name: 事件名称,如 'click''scroll'
    • handler: 事件处理函数。
    • target: 事件目标,可以是 nullListenerType1ListenerType2 类型的对象,也可以是 window
    • options: 事件选项,可能包括 { capture: boolean } 等。
  • useEffect: 用于处理副作用,添加和移除事件监听器。依赖数组包含 namehandlertargetoptions(序列化为字符串,以防 options 是一个对象)。

    • 添加事件监听器:

      • isListenerType1(target): 如果 targetListenerType1 类型,使用 on 函数。
      • isListenerType2(target): 如果 targetListenerType2 类型,使用 target.on 方法。
    • 移除事件监听器:

      • 在副作用清理函数中,使用 offtarget.off 方法。

为什么要区分 on 和 addEventListener?在 JavaScript 和前端开发中,on 和 addEventListener 是两种不同的事件处理机制,addEventListener 是标准的 DOM API 方法,用于在 DOM 元素上注册事件监听器。它允许:1. 支持多次添加同一事件类型的监听器 2. 支持选项参数,例如 capture(是否在捕获阶段调用),once(是否只调用一次),passive(是否可以为被动监听器)。on 是许多 JavaScript 框架、库或自定义事件系统中用于注册事件监听的惯用方法,它的实现可能会因库而异。

3. Lifecycles - useLifecycles

calls mount and unmount callbacks.

使用

import {useLifecycles} from 'react-use';

const Demo = () => {
  useLifecycles(() => console.log('MOUNTED'), () => console.log('UNMOUNTED'));
  return null;
};

源码

import { useEffect } from 'react';

const useLifecycles = (mount, unmount?) => {
  useEffect(() => {
    if (mount) {
      mount();
    }
    return () => {
      if (unmount) {
        unmount();
      }
    };
  }, []);
};

export default useLifecycles;

解释

useLifecycles 是一个自定义的 React Hook,它简化了组件生命周期管理,特别是挂载(mount)和卸载(unmount)时的操作。它是一个实用的工具,可以帮助你在函数组件中更方便地处理副作用的生命周期。

const useLifecycles = (mount, unmount?) => {};
  • 参数:
    • mount: 一个函数,当组件挂载时执行。如果没有提供 mount 函数,组件挂载时不会有额外操作。
    • unmount (可选): 一个函数,当组件卸载时执行。如果没有提供 unmount 函数,组件卸载时不会有额外操作。

示例:n. xx - yy

something

使用

源码

解释

  • 35
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
codepen-clone-react 是一个用 React 框架开发的类似 CodePen 的代码编辑和运行环境的克隆项目。 该项目的源码主要分为几个部分: 首先,项目的文件结构包含了一些主要的文件和文件夹。其中,src 文件夹是我们主要关注的部分,它包含了所有的 React 组件、样式文件以及其他必要的文件。这些组件的文件结构和组织方式遵循了 React 的最佳实践,易于维护和扩展。 其次,该项目的主要功能是提供一个用户友好的代码编辑器界面,使用户能够输入、编辑和运行他们的代码。它具有语法高亮功能,可以根据代码语言自动应用不同的颜色。此外,它还具有代码自动补全、格式化代码和代码错误检查等功能,提供了一个愉快的编码体验。 代码编辑器的核心是基于 CodeMirror 组件实现的。它使用 React 组件进行封装,并通过使用状态管理库如 Redux 来处理用户输入的代码内容。这样用户可以实时编辑和运行他们的代码,而无需刷新页面。 最后,该项目还提供了一个运行结果的输出窗口,用户可以看到他们的代码在浏览器中实际运行的效果。它使用 iframe 标签作为代码运行的容器,并将用户的代码嵌入到 iframe 中执行。 总结来说,codepen-clone-react源码是一个使用 React 框架开发的类似 CodePen 的项目。它提供了一个用户友好的代码编辑和运行环境,实现了代码高亮、自动补全、格式化等功能,并通过 iframe 显示代码运行结果。该项目的源码结构清晰,易于维护和扩展,是一个学习 React 和代码编辑器开发的好例子。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值