当前位置: 首页 > news >正文

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

目录

  • 一、官方介绍
    • 1. Sensors
    • 2. UI
    • 3. Animations
    • 4. Side-Effects
    • 5. Lifecycles
    • 6. State
    • 7. Miscellaneous
  • 二、源码学习
    • 1. Lifecycles - useEffectOnce
    • 2. Lifecycles - useEvent
    • 3. Lifecycles - useLifecycles
    • 4. Lifecycles - useMountedState & useUnmountPromise
      • useMountedState
      • useUnmountPromise
    • 示例:n. xx - yy

一、官方介绍

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 函数,组件卸载时不会有额外操作。

4. Lifecycles - useMountedState & useUnmountPromise

track if component is mounted.

useMountedState

使用

import * as React from 'react';
import {useMountedState} from 'react-use';
const Demo = () => {const isMounted = useMountedState();React.useEffect(() => {setTimeout(() => {if (isMounted()) {// ...} else {// ...}}, 1000);});
};

源码

// useMountedState
import { useCallback, useEffect, useRef } from 'react';
export default function useMountedState(): () => boolean {const mountedRef = useRef<boolean>(false);const get = useCallback(() => mountedRef.current, []);useEffect(() => {mountedRef.current = true;return () => {mountedRef.current = false;};}, []);return get;
}

解释

useMountedState 是一个自定义的 React Hook,用于检测组件的挂载状态。它的主要作用是提供一个函数,这个函数可以用来检查组件是否仍然挂载在 DOM 中。这个自定义 Hook 可以在异步操作或副作用中非常有用,以避免在组件卸载后执行状态更新或其他操作。

让我们逐步解析这个 useMountedState Hook 的实现:

import { useCallback, useEffect, useRef } from 'react';
  • useCallback: 用于创建一个记忆化的回调函数,只有在依赖项发生变化时才会重新创建。
  • useEffect: 用于处理副作用,例如数据获取、事件监听和 DOM 操作等。
  • useRef: 用于创建一个可变的引用对象,该对象在组件的整个生命周期内保持不变。
export default function useMountedState(): () => boolean {const mountedRef = useRef<boolean>(false);const get = useCallback(() => mountedRef.current, []);useEffect(() => {mountedRef.current = true;return () => {mountedRef.current = false;};}, []);return get;
}
  1. const mountedRef = useRef<boolean>(false);

    • 使用 useRef 创建一个可变的引用 mountedRef,其初始值为 false。这个引用对象会在整个组件生命周期内保持不变。
    • mountedRef.current 用于存储组件的挂载状态。
  2. const get = useCallback(() => mountedRef.current, []);

    • 使用 useCallback 创建一个记忆化的回调函数 get,该函数返回 mountedRef.current 的值。
    • 空的依赖数组 [] 表示 get 函数不会因组件重新渲染而重新创建。这样可以确保每次返回的 get 函数引用相同,避免不必要的重新渲染。
  3. useEffect(() => { ... }, []);

    • useEffect 用于执行副作用,在组件挂载时执行并在组件卸载时清理。
    • mountedRef.current = true;:当组件挂载时,将 mountedRef.current 设置为 true
    • return () => { mountedRef.current = false; };:在组件卸载时,将 mountedRef.current 设置为 false,以便清理。
  4. return get;

    • 返回 get 函数,它会根据 mountedRef.current 的值返回组件是否仍然挂载。

useMountedState Hook 的主要目的是提供一个安全的方法来检查组件的挂载状态。这样可以防止在组件卸载后进行状态更新或其他操作,从而避免潜在的内存泄漏和错误。

import React, { useEffect } from 'react';
import useMountedState from './useMountedState';const MyComponent: React.FC = () => {const isMounted = useMountedState();useEffect(() => {const fetchData = async () => {const data = await fetch('/api/data');if (isMounted()) {// 只有在组件仍然挂载时才更新状态// setData(data);}};fetchData();// 清理函数(如果需要)return () => {// 处理组件卸载时的清理逻辑};}, [isMounted]);return <div>My Component</div>;
};

在这个示例中,isMounted() 函数用于检查组件是否仍然挂载。这样可以确保在组件已经卸载后不会尝试更新状态,从而避免错误。

useUnmountPromise

使用


import useUnmountPromise from 'react-use/lib/useUnmountPromise';
const Demo = () => {const mounted = useUnmountPromise();useEffect(async () => {await mounted(someFunction()); // Will not resolve if component un-mounts.});
};

源码

// useUnmountPromise
import { useMemo, useRef } from 'react';
import useEffectOnce from './useEffectOnce';export type Race = <P extends Promise<any>, E = any>(promise: P, onError?: (error: E) => void) => P;const useUnmountPromise = (): Race => {const refUnmounted = useRef(false);useEffectOnce(() => () => {refUnmounted.current = true;});const wrapper = useMemo(() => {const race = <P extends Promise<any>, E>(promise: P, onError?: (error: E) => void) => {const newPromise: P = new Promise((resolve, reject) => {promise.then((result) => {if (!refUnmounted.current) resolve(result);},(error) => {if (!refUnmounted.current) reject(error);else if (onError) onError(error);else console.error('useUnmountPromise', error);});}) as P;return newPromise;};return race;}, []);return wrapper;
};export default useUnmountPromise;

解释

useUnmountPromise 是一个自定义的 React Hook,旨在帮助处理在组件卸载后的 Promise 状态。它确保在组件卸载时不会对未完成的 Promise 进行状态更新,从而避免潜在的内存泄漏和错误。

让我们详细解析这个 Hook 的实现和功能:

import { useMemo, useRef } from 'react';
import useEffectOnce from './useEffectOnce';
  • useMemo: 用于创建一个记忆化的值,以避免在每次渲染时都重新计算。
  • useRef: 用于创建一个可变的引用对象,该对象在组件的整个生命周期内保持不变。
  • useEffectOnce: 自定义的 Hook,确保某个副作用只在组件挂载时执行一次,并在组件卸载时执行清理逻辑。
const useUnmountPromise = (): Race => {const refUnmounted = useRef(false);useEffectOnce(() => () => {refUnmounted.current = true;});const wrapper = useMemo(() => {const race = <P extends Promise<any>, E>(promise: P, onError?: (error: E) => void) => {const newPromise: P = new Promise((resolve, reject) => {promise.then((result) => {if (!refUnmounted.current) resolve(result);},(error) => {if (!refUnmounted.current) reject(error);else if (onError) onError(error);else console.error('useUnmountPromise', error);});}) as P;return newPromise;};return race;}, []);return wrapper;
};
  1. const refUnmounted = useRef(false);

    • 使用 useRef 创建一个引用 refUnmounted,初始值为 false,用于跟踪组件的卸载状态。
  2. useEffectOnce(() => () => { refUnmounted.current = true; });

    • useEffectOnce 确保副作用只在组件挂载时执行一次,并在组件卸载时执行清理。
    • 当组件卸载时,将 refUnmounted.current 设置为 true。这样,后续的 Promise 处理可以检查组件是否已经卸载。
  3. const wrapper = useMemo(() => { ... }, []);

    • useMemo 用于创建一个记忆化的 race 函数,避免每次渲染时都重新创建。
  4. const race = <P extends Promise<any>, E>(promise: P, onError?: (error: E) => void) => { ... }

    • race 函数: 这个函数接受一个 Promise 和一个可选的错误处理函数 onError。它创建并返回一个新的 Promise。
      • then 方法的成功回调中,检查 refUnmounted.current 是否为 false,如果是,则解析结果。
      • then 方法的失败回调中,检查 refUnmounted.current 是否为 false,如果是,则拒绝错误;如果组件已经卸载,则调用 onError 处理错误,或者在控制台记录错误。
  5. return wrapper;

    • 返回 wrapper,这是一个记忆化的 race 函数。

useUnmountPromise Hook 的主要目的是处理组件卸载后 Promise 的状态更新问题。这在异步操作中尤其有用,比如数据获取、计时器等场景中。

import React, { useEffect, useState } from 'react';
import useUnmountPromise from './useUnmountPromise';const MyComponent: React.FC = () => {const race = useUnmountPromise();const [data, setData] = useState<string | null>(null);useEffect(() => {const fetchData = async () => {const promise = fetch('/api/data').then(response => response.json());const result = await race(promise);setData(result);};fetchData();// 清理函数(如果需要)return () => {// 处理组件卸载时的清理逻辑};}, [race]);return <div>{data ? data : 'Loading...'}</div>;
};

在这个示例中,race 函数用于确保在组件卸载后,如果 fetch 请求未完成,不会对组件状态进行更新,从而避免潜在的错误。

示例:n. xx - yy

something

使用

源码

解释

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 电商数据分析的价值
  • 订单类业务创建自增编码
  • Tongweb8074+7049m4 安装TongFlowControl(by lqw)
  • 指针(三)
  • MySQL 数据库自动分区
  • 使用Python恢复Windows、Linux、MacOS回收站中的文件和目录
  • MinIO实战攻略:轻松构建私有云存储解决方案
  • streamlit+wordcloud使用pyinstaller打包遇到的一些坑
  • boost库容器之Circular Buffer功能介绍,及使用示例
  • 神经网络微调技术全解(04)-- Prompt Tuning-可训练提示(Learnable Prompts)
  • 第十章 rust网络编程基础
  • 基于web的停车场管理系统设计与实现-计算机毕设 附源码 16856
  • Kafka·概述
  • 【计算机网络】计算机网络的分层结构
  • 【深度学习与NLP】——深度卷积神经网络AlexNet
  • 【跃迁之路】【699天】程序员高效学习方法论探索系列(实验阶段456-2019.1.19)...
  • canvas 绘制双线技巧
  • Cookie 在前端中的实践
  • django开发-定时任务的使用
  • Java 内存分配及垃圾回收机制初探
  • Java基本数据类型之Number
  • JS函数式编程 数组部分风格 ES6版
  • leetcode386. Lexicographical Numbers
  • LintCode 31. partitionArray 数组划分
  • mac修复ab及siege安装
  • magento 货币换算
  • Node.js 新计划:使用 V8 snapshot 将启动速度提升 8 倍
  • Python连接Oracle
  • vue自定义指令实现v-tap插件
  • 聊聊spring cloud的LoadBalancerAutoConfiguration
  • 设计模式走一遍---观察者模式
  • 算法-插入排序
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • ​sqlite3 --- SQLite 数据库 DB-API 2.0 接口模块​
  • ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTr
  • ######## golang各章节终篇索引 ########
  • (3)nginx 配置(nginx.conf)
  • (3)STL算法之搜索
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (pojstep1.3.1)1017(构造法模拟)
  • (办公)springboot配置aop处理请求.
  • (附源码)ssm户外用品商城 毕业设计 112346
  • (七)MySQL是如何将LRU链表的使用性能优化到极致的?
  • (十)c52学习之旅-定时器实验
  • (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境
  • ./configure,make,make install的作用(转)
  • .NET 中使用 TaskCompletionSource 作为线程同步互斥或异步操作的事件
  • .NET上SQLite的连接
  • .NET委托:一个关于C#的睡前故事
  • @Autowired @Resource @Qualifier的区别
  • @converter 只能用mysql吗_python-MySQLConverter对象没有mysql-connector属性’...
  • @RequestParam详解
  • @TableId注解详细介绍 mybaits 实体类主键注解
  • [ vulhub漏洞复现篇 ] Jetty WEB-INF 文件读取复现CVE-2021-34429
  • [ 攻防演练演示篇 ] 利用通达OA 文件上传漏洞上传webshell获取主机权限