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

目录

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

一、官方介绍

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

使用

源码

解释

Lifecycles - useEffectOnce

a modified useEffect hook that only runs once.

使用

typescript 复制代码
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;
};

源码

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

解释

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

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

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

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

Lifecycles - useEvent

subscribe to events.

使用

typescript 复制代码
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>
  );
};

源码

typescript 复制代码
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 进行类型检查。

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

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

typescript 复制代码
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 等)。

接着看源码:

typescript 复制代码
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 方法。
typescript 复制代码
export type UseEventTarget = ListenerType1 | ListenerType2;
  • UseEventTarget : 联合类型,表示可以是 ListenerType1ListenerType2 中的任何一个。
typescript 复制代码
const defaultTarget = isBrowser ? window : null;
  • defaultTarget : 根据 isBrowser 判断是否在浏览器环境中,如果是则默认为 window,否则为 null
typescript 复制代码
const isListenerType1 = (target: any): target is ListenerType1 => {
  return !!target.addEventListener;
};

const isListenerType2 = (target: any): target is ListenerType2 => {
  return !!target.on;
};
  • isListenerType1isListenerType2 : 类型谓词函数,用于检查 target 是否符合 ListenerType1ListenerType2 类型。
typescript 复制代码
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)。
typescript 复制代码
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 框架、库或自定义事件系统中用于注册事件监听的惯用方法,它的实现可能会因库而异。

Lifecycles - useLifecycles

calls mount and unmount callbacks.

使用

typescript 复制代码
import {useLifecycles} from 'react-use';

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

源码

typescript 复制代码
import { useEffect } from 'react';

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

export default useLifecycles;

解释

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

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

Lifecycles - useMountedState & useUnmountPromise

track if component is mounted.

useMountedState

使用

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

源码

typescript 复制代码
// 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 的实现:

javascript 复制代码
import { useCallback, useEffect, useRef } from 'react';
  • useCallback: 用于创建一个记忆化的回调函数,只有在依赖项发生变化时才会重新创建。
  • useEffect: 用于处理副作用,例如数据获取、事件监听和 DOM 操作等。
  • useRef: 用于创建一个可变的引用对象,该对象在组件的整个生命周期内保持不变。
javascript 复制代码
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 的主要目的是提供一个安全的方法来检查组件的挂载状态。这样可以防止在组件卸载后进行状态更新或其他操作,从而避免潜在的内存泄漏和错误。

javascript 复制代码
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

使用

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

源码

typescript 复制代码
// 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 的实现和功能:

javascript 复制代码
import { useMemo, useRef } from 'react';
import useEffectOnce from './useEffectOnce';
  • useMemo: 用于创建一个记忆化的值,以避免在每次渲染时都重新计算。
  • useRef: 用于创建一个可变的引用对象,该对象在组件的整个生命周期内保持不变。
  • useEffectOnce: 自定义的 Hook,确保某个副作用只在组件挂载时执行一次,并在组件卸载时执行清理逻辑。
javascript 复制代码
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 的状态更新问题。这在异步操作中尤其有用,比如数据获取、计时器等场景中。

javascript 复制代码
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 请求未完成,不会对组件状态进行更新,从而避免潜在的错误。

Lifecycles - usePromise

resolves promise only while component is mounted.

使用

typescript 复制代码
import {usePromise} from 'react-use';

const Demo = ({promise}) => {
  const mounted = usePromise();
  const [value, setValue] = useState();

  useEffect(() => {
    (async () => {
      const value = await mounted(promise);
      // This line will not execute if <Demo> component gets unmounted.
      setValue(value);
    })();
  });
};

源码

typescript 复制代码
import { useCallback } from 'react';
import useMountedState from './useMountedState';

export type UsePromise = () => <T>(promise: Promise<T>) => Promise<T>;

const usePromise: UsePromise = () => {
  const isMounted = useMountedState();
  return useCallback(
    (promise: Promise<any>) =>
      new Promise<any>((resolve, reject) => {
        const onValue = (value) => {
          isMounted() && resolve(value);
        };
        const onError = (error) => {
          isMounted() && reject(error);
        };
        promise.then(onValue, onError);
      }),
    []
  );
};

export default usePromise;

解释

usePromise 是一个自定义的 React Hook,旨在处理异步 Promise 操作,同时确保在组件卸载后不会对已卸载的组件进行状态更新。这个 Hook 使用了另一个自定义 Hook useMountedState 来检查组件的挂载状态。

javascript 复制代码
import { useCallback } from 'react';
import useMountedState from './useMountedState';
  • useCallback: 用于创建一个记忆化的回调函数,避免每次渲染时都重新创建函数。
  • useMountedState: 自定义 Hook,用于检查组件是否仍然挂载(是否处于活动状态)。
javascript 复制代码
const usePromise: UsePromise = () => {
  const isMounted = useMountedState();
  return useCallback(
    (promise: Promise<any>) =>
      new Promise<any>((resolve, reject) => {
        const onValue = (value) => {
          isMounted() && resolve(value);
        };
        const onError = (error) => {
          isMounted() && reject(error);
        };
        promise.then(onValue, onError);
      }),
    []
  );
};
  1. const isMounted = useMountedState();

    • 调用 useMountedState Hook 获取 isMounted 函数,这个函数用于检查组件是否仍然挂载。
  2. return useCallback((promise: Promise<any>) => { ... }, []);

    • 使用 useCallback 创建一个记忆化的函数,只有在依赖项(在此情况下为空数组 [])发生变化时才会重新创建。
    • useCallback 确保这个回调函数在每次组件渲染时保持稳定的引用,从而避免不必要的重新渲染。
  3. new Promise<any>((resolve, reject) => { ... })

    • 返回一个新的 Promise,该 Promise 封装了传入的 promise
    • onValue 函数 :在 promise 成功时调用。如果组件仍然挂载(通过 isMounted() 检查),则解析 Promise。
    • onError 函数 :在 promise 失败时调用。如果组件仍然挂载,则拒绝 Promise;如果组件已经卸载,可以选择执行错误处理函数(但在这里,错误会被直接抛出到控制台)。

usePromise Hook 的主要用途是确保在组件卸载后不会对已卸载的组件进行 Promise 状态更新。这在处理异步操作(如数据获取、定时器等)时尤为重要,以避免潜在的内存泄漏和不必要的状态更新。

javascript 复制代码
import React, { useEffect, useState } from 'react';
import usePromise from './usePromise';

const MyComponent: React.FC = () => {
  const handlePromise = usePromise();
  const [data, setData] = useState<string | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      const promise = fetch('/api/data').then(response => response.json());
      try {
        const result = await handlePromise(promise);
        setData(result);
      } catch (error) {
        console.error('Failed to fetch data:', error);
      }
    };

    fetchData();

    // 清理函数(如果需要)
    return () => {
      // 处理组件卸载时的清理逻辑
    };
  }, [handlePromise]);

  return <div>{data ? data : 'Loading...'}</div>;
};

在这个示例中,handlePromise 用于处理异步 fetch 请求,确保在组件卸载后不会尝试更新组件状态。这样可以避免因为组件卸载导致的错误或警告。

对比 usePromise 和 useUnmountPromise

  1. usePromise
    功能: 封装 Promise,确保只在组件挂载时更新状态。适用场景: 需要确保 Promise 操作的回调仅在组件挂载时执行,适用于简单的 Promise 封装和状态更新。
  2. useUnmountPromise
    功能: 包装 Promise,确保在组件卸载后不会更新状态,并在组件卸载后处理错误。适用场景: 需要处理未完成的 Promise 并进行错误处理,适用于复杂的异步操作和错误处理。

Lifecycles - useLogger

logs in console as component goes through life-cycles.

使用

typescript 复制代码
import {useLogger} from 'react-use';

const Demo = (props) => {
  useLogger('Demo', props);
  return null;
};

//Demo mounted {}
//Demo updated {}
//Demo unmounted

源码

typescript 复制代码
import useEffectOnce from './useEffectOnce';
import useUpdateEffect from './useUpdateEffect';

const useLogger = (componentName: string, ...rest) => {
  useEffectOnce(() => {
    console.log(`${componentName} mounted`, ...rest);
    return () => console.log(`${componentName} unmounted`);
  });

  useUpdateEffect(() => {
    console.log(`${componentName} updated`, ...rest);
  });
};

export default useLogger;

解释

useLogger 是一个自定义的 React Hook,用于在组件的生命周期内记录日志。这种 Hook 可以帮助开发人员在开发过程中调试组件的挂载、更新和卸载行为。它利用了两个其他的自定义 Hook:useEffectOnceuseUpdateEffect。下面我们详细解析 useLogger 的实现和功能。

javascript 复制代码
import useEffectOnce from './useEffectOnce';
import useUpdateEffect from './useUpdateEffect';
  • useEffectOnce: 这个自定义 Hook 确保其副作用(如日志记录)只在组件挂载时执行一次,并在组件卸载时执行清理操作。
  • useUpdateEffect : 这个自定义 Hook 确保其副作用(如日志记录)只在组件更新时执行(即组件的 propsstate 发生变化时)。
javascript 复制代码
const useLogger = (componentName: string, ...rest) => {
  useEffectOnce(() => {
    console.log(`${componentName} mounted`, ...rest);
    return () => console.log(`${componentName} unmounted`);
  });

  useUpdateEffect(() => {
    console.log(`${componentName} updated`, ...rest);
  });
};

export default useLogger;

useLogger 可以帮助开发人员在组件的生命周期内追踪状态和行为。这对于调试和监控组件的生命周期非常有用。

Lifecycles - useMount

calls mount callbacks.

使用

typescript 复制代码
import {useMount} from 'react-use';

const Demo = () => {
  useMount(() => alert('MOUNTED'));
  return null;
};

源码

typescript 复制代码
import useEffectOnce from './useEffectOnce';

const useMount = (fn: () => void) => {
  useEffectOnce(() => {
    fn();
  });
};

export default useMount;

解释

比较简单,不做解释。

Lifecycles - useUnmount

calls unmount callbacks.

使用

typescript 复制代码
import {useUnmount} from 'react-use';

const Demo = () => {
  useUnmount(() => alert('UNMOUNTED'));
  return null;
};

源码

typescript 复制代码
import { useRef } from 'react';
import useEffectOnce from './useEffectOnce';

const useUnmount = (fn: () => any): void => {
  const fnRef = useRef(fn);

  // update the ref each render so if it change the newest callback will be invoked
  fnRef.current = fn;

  useEffectOnce(() => () => fnRef.current());
};

export default useUnmount;

解释

比较简单,不做解释。这里与 useMount 的一个区别在于,useUnMount 传入的回调可能发生变更,因此使用 useRef 来维护。

Lifecycles - useUpdateEffect

run an effect only on updates.

使用

typescript 复制代码
import React from 'react'
import {useUpdateEffect} from 'react-use';

const Demo = () => {
  const [count, setCount] = React.useState(0);
  
  React.useEffect(() => {
    const interval = setInterval(() => {
      setCount(count => count + 1)
    }, 1000)
    
    return () => {
      clearInterval(interval)
    }
  }, [])
  
  useUpdateEffect(() => {
    console.log('count', count) // will only show 1 and beyond
    
    return () => { // *OPTIONAL*
      // do something on unmount
    }
  }) // you can include deps array if necessary

  return <div>Count: {count}</div>
};

源码

typescript 复制代码
import { useEffect } from 'react';
import { useFirstMountState } from './useFirstMountState';

const useUpdateEffect: typeof useEffect = (effect, deps) => {
  const isFirstMount = useFirstMountState();

  useEffect(() => {
    if (!isFirstMount) {
      return effect();
    }
  }, deps);
};

export default useUpdateEffect;

解释

useUpdateEffect 是一个自定义的 React Hook,用于在组件更新时执行副作用(effect),而在组件首次挂载时不会执行。这可以帮助你在组件更新时运行某些逻辑,但避免在初次渲染时运行逻辑。这个 Hook 基于 useEffect 和另一个自定义 Hook useFirstMountState 实现。

javascript 复制代码
import { useEffect } from 'react';
import { useFirstMountState } from './useFirstMountState';
  • useEffect: React 的内置 Hook,用于在组件渲染后执行副作用。
  • useFirstMountState: 自定义 Hook,用于检测组件是否为第一次挂载。
javascript 复制代码
const useUpdateEffect: typeof useEffect = (effect, deps) => {
  const isFirstMount = useFirstMountState();

  useEffect(() => {
    if (!isFirstMount) {
      return effect();
    }
  }, deps);
};
  1. const isFirstMount = useFirstMountState();

    • 调用 useFirstMountState Hook 来判断组件是否是第一次挂载。useFirstMountState 通常是一个自定义 Hook,其功能是返回一个布尔值,指示组件是否为第一次挂载。
typescript 复制代码
import { useRef } from 'react';

export function useFirstMountState(): boolean {
  const isFirst = useRef(true);

  if (isFirst.current) {
    isFirst.current = false;

    return true;
  }

  return isFirst.current;
}
  • useRef : 创建一个持久化的引用对象 isFirstMount,其初始值为 true。在组件首次挂载时,这个引用的值为 true。在挂载完成后,它会被设置为 false,表示组件不再是第一次挂载。
  1. useEffect(() => { if (!isFirstMount) { return effect(); } }, deps);

    • useEffect : 在组件渲染后执行副作用。在这里,它的依赖项数组是 deps,只有当这些依赖项发生变化时,useEffect 才会重新执行。

    • if (!isFirstMount) { return effect(); } : 这个条件检查组件是否为第一次挂载。如果不是第一次挂载(即 isFirstMountfalse),则执行传入的副作用函数 effect。如果是第一次挂载,什么都不做。

useUpdateEffect 主要用于以下场景:

  • 在组件更新时执行副作用 : 当你只希望在组件更新时运行某些逻辑,而不希望在组件首次挂载时运行这些逻辑时,useUpdateEffect 是一个很好的选择。例如,你可能只希望在数据更新时执行某些操作,而不是在组件第一次加载时执行这些操作。
相关推荐
Komorebi.py17 分钟前
【Linux】-学习笔记05
linux·笔记·学习
Mr_Xuhhh22 分钟前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
永乐春秋1 小时前
WEB攻防-通用漏洞&文件上传&js验证&mime&user.ini&语言特性
前端
鸽鸽程序猿1 小时前
【前端】CSS
前端·css
ggdpzhk1 小时前
VUE:基于MVVN的前端js框架
前端·javascript·vue.js
学不会•3 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
活宝小娜6 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点6 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow6 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o6 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app