H5W3
当前位置:H5W3 > 其他技术问题 > 正文

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

故事起源

最近在用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进行封装,最终获得了一个对象,其中包含三个值:

这就让我们省却了将新请求的数据设置为组件的状态等逻辑,让整个代码变得清晰明了了很多。而如果不使用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可以那么做

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介绍

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

故事起源

最近在用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进行封装,最终获得了一个对象,其中包含三个值:

这就让我们省却了将新请求的数据设置为组件的状态等逻辑,让整个代码变得清晰明了了很多。而如果不使用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可以那么做

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介绍

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

故事起源

最近在用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进行封装,最终获得了一个对象,其中包含三个值:

这就让我们省却了将新请求的数据设置为组件的状态等逻辑,让整个代码变得清晰明了了很多。而如果不使用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可以那么做

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介绍

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

故事起源

最近在用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进行封装,最终获得了一个对象,其中包含三个值:

这就让我们省却了将新请求的数据设置为组件的状态等逻辑,让整个代码变得清晰明了了很多。而如果不使用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可以那么做

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介绍

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

image.png

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

总结

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

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

本文地址:H5W3 » React Hook前端请求的第N种方式

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址