乐趣区

前端请求的第N种方式玩转React-Hook

我曾在几年前写过一篇文章——《Jquery ajax, Axios, Fetch 区别之我见》——从原理和使用层面分析了 ajax,axios 和 fetch 的区别。现在,本文从一个小的例子出发,通过使用 react hook,给大家剖析一种新的数据请求方式;并通过这个自定义 HOOK,引出 & 介绍其他的 React Hook 库。废话不多说,我们马上开始。

故事起源

最近在用 umi 写一个项目,然后发现 umi 的网络请求竟然是这么写的:

import {useRequest} from '@umijs/hooks';
import Mock from 'mockjs';
import React from 'react';

function getUsername(): Promise<string> {
  return new Promise(resolve => {setTimeout(() => {resolve(Mock.mock('@name'));
    }, 1000);
  });
}

export default () => {const { data, error, loading} = useRequest(getUsername)
  if (error) {return <div>failed to load</div>}
  if (loading) {return <div>loading...</div>}
  return <div>Username: {data}</div>
}

PS:试一下

在这个例子中,我们使用 useRequest 对异步请求 getUsername 进行封装,最终获得了一个对象,其中包含三个值:

data: 正常请求返回的数据
error: 异常请求的错误信息
loading: 是否在加载 / 请求的状态

这就让我们 省却了将新请求的数据设置为组件的状态等逻辑,让整个代码变得清晰明了了很多。而如果不使用 useRequest,我们代码应该是下面这样:

import {useRequest} from "@umijs/hooks";
import Mock from "mockjs";
import React from "react";

const {useState, useEffect} = React;

function getUsername(): Promise<string> {
  return new Promise(resolve => {setTimeout(() => {resolve(Mock.mock("@name"));
    }, 1000);
  });
}

export default () => {const [username, setUsername] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  // 异步请求还是要封装一下
  async function getUsernameAsync() {setLoading(true);
    const res = await getUsername();
    // 在本例子中,getUsername 永远返回的都是请求正确的值,我们这边假设当请求失败时,它会返回一个 error 对象
    if (typeof res === "string") {setUsername(res);
    } else {setError(res);
    }
    setLoading(false);
  }
  // 发起请求
  useEffect(() => {getUsernameAsync();
  }, []);

  if (error) {return <div>failed to load</div>;}
  if (loading) {return <div>loading...</div>;}
  return <div>Username: {username}</div>;
};

这下就应该很明确了,useRequest 帮我们抹平了三个 state 值,让我们可以直接去使用它们。这就有点神奇了,它是怎么实现的呢?是否还有什么其他的能力?马上进入下一趴!

实现原理

在本节,我们通过对 react-use 的 useAsync 进行代码分析,来解释上一节中为什么 @umijs/hooks 的 useRequest 可以那么做

useAsync 代码相对简单,useRequest 的代码中掺杂了很多它的其他特性,本质上它们的原理是一致的。

react-use: [项目地址]一个 react hook 函数库,相当于 react hook 的 JQuery,提供多种基本的基础 Hook 封装能力:如发送请求,获取 / 设置 cookie,获取鼠标的位置等等。

PS:react-use 的中文翻译版本还是针对 V8.1.3 的,而现在的版本是 V15.1.1,所以可以直接看英文文档。

首先还是先贴一下使用方式:

import {useAsync} from 'react-use';

function getUsername(): Promise<string> {
  return new Promise(resolve => {setTimeout(() => {resolve(Mock.mock("@name"));
    }, 1000);
  });
}

const Demo = () => {const state = useAsync(getUsername);

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

可以看到,使用方法都是一样的,那么下面我们来看下它们是如何实现的——因为 useAsync 引用了自身的 useAsyncFn,所以我们下面直接来分析 useAsyncFn。

/* eslint-disable */
// import 暂时都不关心
import {DependencyList, useCallback, useState, useRef} from 'react';
import useMountedState from './useMountedState';
import {FnReturningPromise, PromiseType} from './util';
// AsyncState 有四种状态
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;
    };
// ts 的类型暂时也都不关心
type StateFromFnReturningPromise<T extends FnReturningPromise> = AsyncState<PromiseType<ReturnType<T>>>;
export type AsyncFnReturn<T extends FnReturningPromise = FnReturningPromise> = [StateFromFnReturningPromise<T>, T];
// 正文,接受三个参数,一个异步请求函数,一个数组类型的依赖,一个初始状态,默认是 loading=false
export default function useAsyncFn<T extends FnReturningPromise>(
  fn: T,
  deps: DependencyList = [],
  initialState: StateFromFnReturningPromise<T> = {loading: false}
): AsyncFnReturn<T> {
  // 记录是第几次调用
  const lastCallId = useRef(0);
  // 判断当前组件是否 mounted 完成
  const isMounted = useMountedState();
  // 设置状态
  const [state, set] = useState<StateFromFnReturningPromise<T>>(initialState);
  // useCallback 就是用来性能优化的,保证返回同一个回调函数,因此,我们直接看它内部的回调函数就行
  const callback = useCallback((...args: Parameters<T>): ReturnType<T> => {
    // 第几次调用
    const callId = ++lastCallId.current;
    // 直接设置为 loading 为 true
    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);
  // 返回两个值,一个是 state,一个是封装好的 callback
  return [state, (callback as unknown) as T];
}

这就是 useAsyncFn 的实现逻辑,可以看到本质上和我们第一节上的疯狂设置状态原理也差不多,但是这样对请求的封装却打开了新的大门,具体有哪些能力呢?

SWR 和 useRequest 介绍

毫无疑问,第一节和第二节对异步请求的封装避免了我们在不同的请求中反复的处理状态,是能大幅提效的。那是否还有更牛逼的能力呢?它们来了!

SWR:[项目地址]一个 React hook 的异步请求库。提供了异步请求的多种能力,例如:swr(stale-while-revalidate,先返回之前请求的缓存数据,再重新请求并刷新)能力,分页,屏幕聚焦发送请求等。useRequest 也是借鉴了 SWR 的思路。

我曾在几年前写过一篇文章——《Jquery ajax, Axios, Fetch 区别之我见》——从原理和使用层面分析了 ajax,axios 和 fetch 的区别。现在,本文从一个小的例子出发,通过使用 react hook,给大家剖析一种新的数据请求方式;并通过这个自定义 HOOK,引出 & 介绍其他的 React Hook 库。废话不多说,我们马上开始。

故事起源

最近在用 umi 写一个项目,然后发现 umi 的网络请求竟然是这么写的:

import {useRequest} from '@umijs/hooks';
import Mock from 'mockjs';
import React from 'react';

function getUsername(): Promise<string> {
  return new Promise(resolve => {setTimeout(() => {resolve(Mock.mock('@name'));
    }, 1000);
  });
}

export default () => {const { data, error, loading} = useRequest(getUsername)
  if (error) {return <div>failed to load</div>}
  if (loading) {return <div>loading...</div>}
  return <div>Username: {data}</div>
}

PS:试一下

在这个例子中,我们使用 useRequest 对异步请求 getUsername 进行封装,最终获得了一个对象,其中包含三个值:

data: 正常请求返回的数据
error: 异常请求的错误信息
loading: 是否在加载 / 请求的状态

这就让我们 省却了将新请求的数据设置为组件的状态等逻辑,让整个代码变得清晰明了了很多。而如果不使用 useRequest,我们代码应该是下面这样:

import {useRequest} from "@umijs/hooks";
import Mock from "mockjs";
import React from "react";

const {useState, useEffect} = React;

function getUsername(): Promise<string> {
  return new Promise(resolve => {setTimeout(() => {resolve(Mock.mock("@name"));
    }, 1000);
  });
}

export default () => {const [username, setUsername] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  // 异步请求还是要封装一下
  async function getUsernameAsync() {setLoading(true);
    const res = await getUsername();
    // 在本例子中,getUsername 永远返回的都是请求正确的值,我们这边假设当请求失败时,它会返回一个 error 对象
    if (typeof res === "string") {setUsername(res);
    } else {setError(res);
    }
    setLoading(false);
  }
  // 发起请求
  useEffect(() => {getUsernameAsync();
  }, []);

  if (error) {return <div>failed to load</div>;}
  if (loading) {return <div>loading...</div>;}
  return <div>Username: {username}</div>;
};

这下就应该很明确了,useRequest 帮我们抹平了三个 state 值,让我们可以直接去使用它们。这就有点神奇了,它是怎么实现的呢?是否还有什么其他的能力?马上进入下一趴!

实现原理

在本节,我们通过对 react-use 的 useAsync 进行代码分析,来解释上一节中为什么 @umijs/hooks 的 useRequest 可以那么做

useAsync 代码相对简单,useRequest 的代码中掺杂了很多它的其他特性,本质上它们的原理是一致的。

react-use: [项目地址]一个 react hook 函数库,相当于 react hook 的 JQuery,提供多种基本的基础 Hook 封装能力:如发送请求,获取 / 设置 cookie,获取鼠标的位置等等。

PS:react-use 的中文翻译版本还是针对 V8.1.3 的,而现在的版本是 V15.1.1,所以可以直接看英文文档。

首先还是先贴一下使用方式:

import {useAsync} from 'react-use';

function getUsername(): Promise<string> {
  return new Promise(resolve => {setTimeout(() => {resolve(Mock.mock("@name"));
    }, 1000);
  });
}

const Demo = () => {const state = useAsync(getUsername);

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

可以看到,使用方法都是一样的,那么下面我们来看下它们是如何实现的——因为 useAsync 引用了自身的 useAsyncFn,所以我们下面直接来分析 useAsyncFn。

/* eslint-disable */
// import 暂时都不关心
import {DependencyList, useCallback, useState, useRef} from 'react';
import useMountedState from './useMountedState';
import {FnReturningPromise, PromiseType} from './util';
// AsyncState 有四种状态
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;
    };
// ts 的类型暂时也都不关心
type StateFromFnReturningPromise<T extends FnReturningPromise> = AsyncState<PromiseType<ReturnType<T>>>;
export type AsyncFnReturn<T extends FnReturningPromise = FnReturningPromise> = [StateFromFnReturningPromise<T>, T];
// 正文,接受三个参数,一个异步请求函数,一个数组类型的依赖,一个初始状态,默认是 loading=false
export default function useAsyncFn<T extends FnReturningPromise>(
  fn: T,
  deps: DependencyList = [],
  initialState: StateFromFnReturningPromise<T> = {loading: false}
): AsyncFnReturn<T> {
  // 记录是第几次调用
  const lastCallId = useRef(0);
  // 判断当前组件是否 mounted 完成
  const isMounted = useMountedState();
  // 设置状态
  const [state, set] = useState<StateFromFnReturningPromise<T>>(initialState);
  // useCallback 就是用来性能优化的,保证返回同一个回调函数,因此,我们直接看它内部的回调函数就行
  const callback = useCallback((...args: Parameters<T>): ReturnType<T> => {
    // 第几次调用
    const callId = ++lastCallId.current;
    // 直接设置为 loading 为 true
    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);
  // 返回两个值,一个是 state,一个是封装好的 callback
  return [state, (callback as unknown) as T];
}

这就是 useAsyncFn 的实现逻辑,可以看到本质上和我们第一节上的疯狂设置状态原理也差不多,但是这样对请求的封装却打开了新的大门,具体有哪些能力呢?

SWR 和 useRequest 介绍

毫无疑问,第一节和第二节对异步请求的封装避免了我们在不同的请求中反复的处理状态,是能大幅提效的。那是否还有更牛逼的能力呢?它们来了!

SWR:[项目地址]一个 React hook 的异步请求库。提供了异步请求的多种能力,例如:swr(stale-while-revalidate,先返回之前请求的缓存数据,再重新请求并刷新)能力,分页,屏幕聚焦发送请求等。useRequest 也是借鉴了 SWR 的思路。

我曾在几年前写过一篇文章——《Jquery ajax, Axios, Fetch 区别之我见》——从原理和使用层面分析了 ajax,axios 和 fetch 的区别。现在,本文从一个小的例子出发,通过使用 react hook,给大家剖析一种新的数据请求方式;并通过这个自定义 HOOK,引出 & 介绍其他的 React Hook 库。废话不多说,我们马上开始。

故事起源

最近在用 umi 写一个项目,然后发现 umi 的网络请求竟然是这么写的:

import {useRequest} from '@umijs/hooks';
import Mock from 'mockjs';
import React from 'react';

function getUsername(): Promise<string> {
  return new Promise(resolve => {setTimeout(() => {resolve(Mock.mock('@name'));
    }, 1000);
  });
}

export default () => {const { data, error, loading} = useRequest(getUsername)
  if (error) {return <div>failed to load</div>}
  if (loading) {return <div>loading...</div>}
  return <div>Username: {data}</div>
}

PS:试一下

在这个例子中,我们使用 useRequest 对异步请求 getUsername 进行封装,最终获得了一个对象,其中包含三个值:

data: 正常请求返回的数据
error: 异常请求的错误信息
loading: 是否在加载 / 请求的状态

这就让我们 省却了将新请求的数据设置为组件的状态等逻辑,让整个代码变得清晰明了了很多。而如果不使用 useRequest,我们代码应该是下面这样:

import {useRequest} from "@umijs/hooks";
import Mock from "mockjs";
import React from "react";

const {useState, useEffect} = React;

function getUsername(): Promise<string> {
  return new Promise(resolve => {setTimeout(() => {resolve(Mock.mock("@name"));
    }, 1000);
  });
}

export default () => {const [username, setUsername] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  // 异步请求还是要封装一下
  async function getUsernameAsync() {setLoading(true);
    const res = await getUsername();
    // 在本例子中,getUsername 永远返回的都是请求正确的值,我们这边假设当请求失败时,它会返回一个 error 对象
    if (typeof res === "string") {setUsername(res);
    } else {setError(res);
    }
    setLoading(false);
  }
  // 发起请求
  useEffect(() => {getUsernameAsync();
  }, []);

  if (error) {return <div>failed to load</div>;}
  if (loading) {return <div>loading...</div>;}
  return <div>Username: {username}</div>;
};

这下就应该很明确了,useRequest 帮我们抹平了三个 state 值,让我们可以直接去使用它们。这就有点神奇了,它是怎么实现的呢?是否还有什么其他的能力?马上进入下一趴!

实现原理

在本节,我们通过对 react-use 的 useAsync 进行代码分析,来解释上一节中为什么 @umijs/hooks 的 useRequest 可以那么做

useAsync 代码相对简单,useRequest 的代码中掺杂了很多它的其他特性,本质上它们的原理是一致的。

react-use: [项目地址]一个 react hook 函数库,相当于 react hook 的 JQuery,提供多种基本的基础 Hook 封装能力:如发送请求,获取 / 设置 cookie,获取鼠标的位置等等。

PS:react-use 的中文翻译版本还是针对 V8.1.3 的,而现在的版本是 V15.1.1,所以可以直接看英文文档。

首先还是先贴一下使用方式:

import {useAsync} from 'react-use';

function getUsername(): Promise<string> {
  return new Promise(resolve => {setTimeout(() => {resolve(Mock.mock("@name"));
    }, 1000);
  });
}

const Demo = () => {const state = useAsync(getUsername);

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

可以看到,使用方法都是一样的,那么下面我们来看下它们是如何实现的——因为 useAsync 引用了自身的 useAsyncFn,所以我们下面直接来分析 useAsyncFn。

/* eslint-disable */
// import 暂时都不关心
import {DependencyList, useCallback, useState, useRef} from 'react';
import useMountedState from './useMountedState';
import {FnReturningPromise, PromiseType} from './util';
// AsyncState 有四种状态
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;
    };
// ts 的类型暂时也都不关心
type StateFromFnReturningPromise<T extends FnReturningPromise> = AsyncState<PromiseType<ReturnType<T>>>;
export type AsyncFnReturn<T extends FnReturningPromise = FnReturningPromise> = [StateFromFnReturningPromise<T>, T];
// 正文,接受三个参数,一个异步请求函数,一个数组类型的依赖,一个初始状态,默认是 loading=false
export default function useAsyncFn<T extends FnReturningPromise>(
  fn: T,
  deps: DependencyList = [],
  initialState: StateFromFnReturningPromise<T> = {loading: false}
): AsyncFnReturn<T> {
  // 记录是第几次调用
  const lastCallId = useRef(0);
  // 判断当前组件是否 mounted 完成
  const isMounted = useMountedState();
  // 设置状态
  const [state, set] = useState<StateFromFnReturningPromise<T>>(initialState);
  // useCallback 就是用来性能优化的,保证返回同一个回调函数,因此,我们直接看它内部的回调函数就行
  const callback = useCallback((...args: Parameters<T>): ReturnType<T> => {
    // 第几次调用
    const callId = ++lastCallId.current;
    // 直接设置为 loading 为 true
    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);
  // 返回两个值,一个是 state,一个是封装好的 callback
  return [state, (callback as unknown) as T];
}

这就是 useAsyncFn 的实现逻辑,可以看到本质上和我们第一节上的疯狂设置状态原理也差不多,但是这样对请求的封装却打开了新的大门,具体有哪些能力呢?

SWR 和 useRequest 介绍

毫无疑问,第一节和第二节对异步请求的封装避免了我们在不同的请求中反复的处理状态,是能大幅提效的。那是否还有更牛逼的能力呢?它们来了!

SWR:[项目地址]一个 React hook 的异步请求库。提供了异步请求的多种能力,例如:swr(stale-while-revalidate,先返回之前请求的缓存数据,再重新请求并刷新)能力,分页,屏幕聚焦发送请求等。useRequest 也是借鉴了 SWR 的思路。

我曾在几年前写过一篇文章——《Jquery ajax, Axios, Fetch 区别之我见》——从原理和使用层面分析了 ajax,axios 和 fetch 的区别。现在,本文从一个小的例子出发,通过使用 react hook,给大家剖析一种新的数据请求方式;并通过这个自定义 HOOK,引出 & 介绍其他的 React Hook 库。废话不多说,我们马上开始。

故事起源

最近在用 umi 写一个项目,然后发现 umi 的网络请求竟然是这么写的:

import {useRequest} from '@umijs/hooks';
import Mock from 'mockjs';
import React from 'react';

function getUsername(): Promise<string> {
  return new Promise(resolve => {setTimeout(() => {resolve(Mock.mock('@name'));
    }, 1000);
  });
}

export default () => {const { data, error, loading} = useRequest(getUsername)
  if (error) {return <div>failed to load</div>}
  if (loading) {return <div>loading...</div>}
  return <div>Username: {data}</div>
}

PS:试一下

在这个例子中,我们使用 useRequest 对异步请求 getUsername 进行封装,最终获得了一个对象,其中包含三个值:

data: 正常请求返回的数据
error: 异常请求的错误信息
loading: 是否在加载 / 请求的状态

这就让我们 省却了将新请求的数据设置为组件的状态等逻辑,让整个代码变得清晰明了了很多。而如果不使用 useRequest,我们代码应该是下面这样:

import {useRequest} from "@umijs/hooks";
import Mock from "mockjs";
import React from "react";

const {useState, useEffect} = React;

function getUsername(): Promise<string> {
  return new Promise(resolve => {setTimeout(() => {resolve(Mock.mock("@name"));
    }, 1000);
  });
}

export default () => {const [username, setUsername] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  // 异步请求还是要封装一下
  async function getUsernameAsync() {setLoading(true);
    const res = await getUsername();
    // 在本例子中,getUsername 永远返回的都是请求正确的值,我们这边假设当请求失败时,它会返回一个 error 对象
    if (typeof res === "string") {setUsername(res);
    } else {setError(res);
    }
    setLoading(false);
  }
  // 发起请求
  useEffect(() => {getUsernameAsync();
  }, []);

  if (error) {return <div>failed to load</div>;}
  if (loading) {return <div>loading...</div>;}
  return <div>Username: {username}</div>;
};

这下就应该很明确了,useRequest 帮我们抹平了三个 state 值,让我们可以直接去使用它们。这就有点神奇了,它是怎么实现的呢?是否还有什么其他的能力?马上进入下一趴!

实现原理

在本节,我们通过对 react-use 的 useAsync 进行代码分析,来解释上一节中为什么 @umijs/hooks 的 useRequest 可以那么做

useAsync 代码相对简单,useRequest 的代码中掺杂了很多它的其他特性,本质上它们的原理是一致的。

react-use: [项目地址]一个 react hook 函数库,相当于 react hook 的 JQuery,提供多种基本的基础 Hook 封装能力:如发送请求,获取 / 设置 cookie,获取鼠标的位置等等。

PS:react-use 的中文翻译版本还是针对 V8.1.3 的,而现在的版本是 V15.1.1,所以可以直接看英文文档。

首先还是先贴一下使用方式:

import {useAsync} from 'react-use';

function getUsername(): Promise<string> {
  return new Promise(resolve => {setTimeout(() => {resolve(Mock.mock("@name"));
    }, 1000);
  });
}

const Demo = () => {const state = useAsync(getUsername);

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

可以看到,使用方法都是一样的,那么下面我们来看下它们是如何实现的——因为 useAsync 引用了自身的 useAsyncFn,所以我们下面直接来分析 useAsyncFn。

/* eslint-disable */
// import 暂时都不关心
import {DependencyList, useCallback, useState, useRef} from 'react';
import useMountedState from './useMountedState';
import {FnReturningPromise, PromiseType} from './util';
// AsyncState 有四种状态
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;
    };
// ts 的类型暂时也都不关心
type StateFromFnReturningPromise<T extends FnReturningPromise> = AsyncState<PromiseType<ReturnType<T>>>;
export type AsyncFnReturn<T extends FnReturningPromise = FnReturningPromise> = [StateFromFnReturningPromise<T>, T];
// 正文,接受三个参数,一个异步请求函数,一个数组类型的依赖,一个初始状态,默认是 loading=false
export default function useAsyncFn<T extends FnReturningPromise>(
  fn: T,
  deps: DependencyList = [],
  initialState: StateFromFnReturningPromise<T> = {loading: false}
): AsyncFnReturn<T> {
  // 记录是第几次调用
  const lastCallId = useRef(0);
  // 判断当前组件是否 mounted 完成
  const isMounted = useMountedState();
  // 设置状态
  const [state, set] = useState<StateFromFnReturningPromise<T>>(initialState);
  // useCallback 就是用来性能优化的,保证返回同一个回调函数,因此,我们直接看它内部的回调函数就行
  const callback = useCallback((...args: Parameters<T>): ReturnType<T> => {
    // 第几次调用
    const callId = ++lastCallId.current;
    // 直接设置为 loading 为 true
    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);
  // 返回两个值,一个是 state,一个是封装好的 callback
  return [state, (callback as unknown) as T];
}

这就是 useAsyncFn 的实现逻辑,可以看到本质上和我们第一节上的疯狂设置状态原理也差不多,但是这样对请求的封装却打开了新的大门,具体有哪些能力呢?

SWR 和 useRequest 介绍

毫无疑问,第一节和第二节对异步请求的封装避免了我们在不同的请求中反复的处理状态,是能大幅提效的。那是否还有更牛逼的能力呢?它们来了!

SWR:[项目地址]一个 React hook 的异步请求库。提供了异步请求的多种能力,例如:swr(stale-while-revalidate,先返回之前请求的缓存数据,再重新请求并刷新)能力,分页,屏幕聚焦发送请求等。useRequest 也是借鉴了 SWR 的思路。

useRequest: [项目地址]蚂蚁中台标准请求 Hook 仓库。提供了多种能力,并被内置到 umi 中。

具体的能力大家可以看《useRequest- 蚂蚁中台标准请求 Hooks》,已经很详尽了,我就不过多介绍了。

总结

本文从一个 umi request 的使用 case 入手,分析了它的原理。并在这个过程中,分别介绍了 react-use 这个通用基础 hook 仓库,和 SWR/useRequest 这两个异步请求 hook 仓库。通过使用它们的能力,可以大幅提升我们的代码编写效率。

如果你还没有使用 react hook 的话,请马上加入到 react hook 的大家庭中,因为随着它的发展,它真的拥有了很多 class component 所不具备的特性。

退出移动版