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

目录

  • 一、官方介绍
    • [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)
    • [Side-effects - useAsync, useAsyncFn, and useAsyncRetry](#Side-effects - useAsync, useAsyncFn, and useAsyncRetry)

一、官方介绍

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

使用

源码

解释

Side-effects - useAsync, useAsyncFn, and useAsyncRetry

resolves an async function.

useAsync

使用

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

const Demo = ({url}) => {
  const state = useAsync(async () => {
    const response = await fetch(url);
    const result = await response.text();
    return result
  }, [url]);

  return (
    <div>
      {state.loading
        ? <div>Loading...</div>
        : state.error
          ? <div>Error: {state.error.message}</div>
          : <div>Value: {state.value}</div>
      }
    </div>
  );
};

源码

typescript 复制代码
import { DependencyList, useEffect } from 'react';
import useAsyncFn from './useAsyncFn';
import { FunctionReturningPromise } from './misc/types';

export { AsyncState, AsyncFnReturn } from './useAsyncFn';

export default function useAsync<T extends FunctionReturningPromise>(
  fn: T,
  deps: DependencyList = []
) {
  const [state, callback] = useAsyncFn(fn, deps, {
    loading: true,
  });

  useEffect(() => {
    callback();
  }, [callback]);

  return state;
}

解释

useAsync 这个 hook 用于处理异步操作,并且在 React 组件中管理它们的状态。我们逐步解析这个 API。

javascript 复制代码
import { DependencyList, useEffect } from 'react';
import useAsyncFn from './useAsyncFn';
import { FunctionReturningPromise } from './misc/types';
//export type FunctionReturningPromise = (...args: any[]) => Promise<any>;
  • DependencyList: 这是 react 的一个类型,表示依赖项数组的类型,通常用于 useEffect 的第二个参数。
  • useEffect: React 的 hook,用于处理副作用。
  • useAsyncFn: 另一个自定义 hook,它处理异步函数的执行和状态管理。在下方会进行详细介绍。
  • FunctionReturningPromise: 一个 TypeScript 类型,表示返回 Promise 的函数类型。
javascript 复制代码
export { AsyncState, AsyncFnReturn } from './useAsyncFn';

这行代码从 ./useAsyncFn 文件中导出了 AsyncStateAsyncFnReturn 类型或接口。它们通常用于描述异步操作的状态和结果。

函数 useAsync 实现:

  • 参数:

    • fn: T: 这是一个泛型参数,表示返回 Promise 的函数。这个函数将被执行并处理异步操作。
    • deps: DependencyList = []: 这是依赖项数组,默认为空数组,决定了 useEffect 何时重新执行。这个参数会传递给 useAsyncFn
  • 内部逻辑:

    • const [state, callback] = useAsyncFn(fn, deps, { loading: true });

      • useAsyncFn 是一个自定义 hook,用于处理异步操作。它返回一个包含状态和回调函数的数组。
      • state 是异步操作的状态(例如:loadingerrorresult)。
      • callback 是一个函数,用于触发异步操作。
    • useEffect(() => { callback(); }, [callback]);

      • 这个 useEffect 会在组件挂载时调用 callback。这样 fn 函数在组件加载时就会被执行一次。
      • 依赖项数组中包含 callback,意味着只有当 callback 改变时,useEffect 才会重新执行。
  • 返回值:

    • return state;
      • useAsync 返回的是 useAsyncFn 返回的 state,它包含异步操作的状态信息。

useAsyncFn

useAsync 就是基于 useAsyncFn 封装的,只是仅返回 state。

使用

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

const Demo = ({url}) => {
  const [state, doFetch] = useAsyncFn(async () => {
    const response = await fetch(url);
    const result = await response.text();
    return result
  }, [url]);

  return (
    <div>
      {state.loading
        ? <div>Loading...</div>
        : state.error
          ? <div>Error: {state.error.message}</div>
          : <div>Value: {state.value}</div>
      }
      <button onClick={() => doFetch()}>Start loading</button>
    </div>
  );
};

源码

typescript 复制代码
import { DependencyList, useCallback, useRef, useState } from 'react';
import useMountedState from './useMountedState';
import { FunctionReturningPromise, PromiseType } from './misc/types';

export type AsyncState<T> =
  | {
      loading: boolean;
      error?: undefined;
      value?: undefined;
    }
  | {
      loading: true;
      error?: Error | undefined;
      value?: T;
    }
  | {
      loading: false;
      error: Error;
      value?: undefined;
    }
  | {
      loading: false;
      error?: undefined;
      value: T;
    };

type StateFromFunctionReturningPromise<T extends FunctionReturningPromise> = AsyncState<
  PromiseType<ReturnType<T>>
>;

export type AsyncFnReturn<T extends FunctionReturningPromise = FunctionReturningPromise> = [
  StateFromFunctionReturningPromise<T>,
  T
];

export default function useAsyncFn<T extends FunctionReturningPromise>(
  fn: T,
  deps: DependencyList = [],
  initialState: StateFromFunctionReturningPromise<T> = { loading: false }
): AsyncFnReturn<T> {
  const lastCallId = useRef(0);
  const isMounted = useMountedState();
  const [state, set] = useState<StateFromFunctionReturningPromise<T>>(initialState);

  const callback = useCallback((...args: Parameters<T>): ReturnType<T> => {
    const callId = ++lastCallId.current;

    if (!state.loading) {
      set((prevState) => ({ ...prevState, loading: true }));
    }

    return fn(...args).then(
      (value) => {
        isMounted() && callId === lastCallId.current && set({ value, loading: false });

        return value;
      },
      (error) => {
        isMounted() && callId === lastCallId.current && set({ error, loading: false });

        return error;
      }
    ) as ReturnType<T>;
  }, deps);

  return [state, callback as unknown as T];
}

解释

useAsyncFn 这个 hook 主要用于管理异步函数的状态,并提供一个函数来执行异步操作,同时保持组件状态的同步。我们来逐步解析这个 API。

  1. 类型定义
typescript 复制代码
export type AsyncState<T> =
  | {
      loading: boolean;
      error?: undefined;
      value?: undefined;
    }
  | {
      loading: true;
      error?: Error | undefined;
      value?: T;
    }
  | {
      loading: false;
      error: Error;
      value?: undefined;
    }
  | {
      loading: false;
      error?: undefined;
      value: T;
    };
  • AsyncState<T> :
    • 这是一个 TypeScript 类型,表示异步操作的不同状态。它可以是:
      • 正在加载 (loading: true),没有值和错误。
      • 加载完成,有值 (loading: false, value: T)。
      • 加载完成,有错误 (loading: false, error: Error)。
      • 既不是加载中,也没有错误和值 (loading: falsevalue: T),或者只有错误。
typescript 复制代码
//export type PromiseType<P extends Promise<any>> = P extends Promise<infer T> ? T : never;
//export type FunctionReturningPromise = (...args: any[]) => Promise<any>;
type StateFromFunctionReturningPromise<T extends FunctionReturningPromise> = AsyncState<
  PromiseType<ReturnType<T>>
>;
  • StateFromFunctionReturningPromise<T> :
    • 这个类型基于 AsyncStatePromiseType 生成,用于从返回 Promise 的函数中提取状态类型。即 useAsyncFn 接收的 fn 是一个动态的,其返回的数据类型是动态的,因此用 T 泛型来定义,并提取其对应的 AsyncState。
typescript 复制代码
export type AsyncFnReturn<T extends FunctionReturningPromise = FunctionReturningPromise> = [
  StateFromFunctionReturningPromise<T>,
  T
];
  • AsyncFnReturn<T> :
    • 这是 useAsyncFn 返回的类型,包含两个元素:
      • StateFromFunctionReturningPromise<T>: 异步操作的状态。
      • T: 执行异步操作的函数。
  1. useAsyncFn 函数实现
  • 参数:

    • fn: T: 这是一个返回 Promise 的函数。
    • deps: DependencyList = []: 依赖项数组,默认为空数组,用于 useCallback
    • initialState: StateFromFunctionReturningPromise<T> = { loading: false }: 初始状态,默认为 { loading: false }
  • 内部逻辑:

    • const lastCallId = useRef(0);: 用于追踪最近一次异步调用的 ID,以便在异步操作完成时能正确更新状态。每次新增调用会 const callId = ++lastCallId.current;,而当 callId === lastCallId.current 为真才表示是本次异步返回值。

    • const isMounted = useMountedState();: 一个 hook 用于检查组件是否仍然挂载。这是为了避免在组件卸载后尝试更新状态。一般在异步调用后,需要更新变量时要结合该状态使用。

    • const [state, set] = useState<StateFromFunctionReturningPromise<T>>(initialState);: 组件的状态,用于存储异步操作的当前状态。

    • callback:

      • 使用 useCallback 包装的函数,会在组件挂载时执行异步操作。
      • 每次调用 callback 时,会生成一个新的 callId,并设置 loading 状态为 true
      • 调用 fn(...args),然后处理其结果:
        • 如果组件仍然挂载且 callId 匹配,则更新状态为操作的结果(成功或失败)。
  • 返回值:

    • [state, callback as unknown as T]: 返回一个数组,第一个元素是当前的异步状态,第二个元素是执行异步操作的函数。注意 callback 被强制转换为 T 类型,以便可以用作函数。

useAsyncRetry

使用

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

const Demo = ({url}) => {
  const state = useAsyncRetry(async () => {
    const response = await fetch(url);
    const result = await response.text();
    return result;
  }, [url]);

  return (
    <div>
      {state.loading
        ? <div>Loading...</div>
        : state.error
          ? <div>Error: {state.error.message}</div>
          : <div>Value: {state.value}</div>
      }
      {!loading && <button onClick={() => state.retry()}>Start loading</button>}
    </div>
  );
};

源码

typescript 复制代码
import { DependencyList, useCallback, useState } from 'react';
import useAsync, { AsyncState } from './useAsync';

export type AsyncStateRetry<T> = AsyncState<T> & {
  retry(): void;
};

const useAsyncRetry = <T>(fn: () => Promise<T>, deps: DependencyList = []) => {
  const [attempt, setAttempt] = useState<number>(0);
  const state = useAsync(fn, [...deps, attempt]);

  const stateLoading = state.loading;
  const retry = useCallback(() => {
    if (stateLoading) {
      if (process.env.NODE_ENV === 'development') {
        console.log(
          'You are calling useAsyncRetry hook retry() method while loading in progress, this is a no-op.'
        );
      }

      return;
    }

    setAttempt((currentAttempt) => currentAttempt + 1);
  }, [...deps, stateLoading]);

  return { ...state, retry };
};

export default useAsyncRetry;

解释

结合前面两个 api 的实现,比较简单,不做更多解释。

相关推荐
我要洋人死10 分钟前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人21 分钟前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人22 分钟前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR27 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香29 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q24985969332 分钟前
前端预览word、excel、ppt
前端·word·excel
数据与后端架构提升之路34 分钟前
从神经元到神经网络:深度学习的进化之旅
人工智能·神经网络·学习
小华同学ai37 分钟前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
一行143 分钟前
电脑蓝屏debug学习
学习·电脑
Gavin_9151 小时前
【JavaScript】模块化开发
前端·javascript·vue.js