想必经常写 管理后台 调用 HTTP 增删除改查接口的 React 小伙伴们可能对 axios-hooks 已经十分熟悉了,不熟悉的小伙伴们也别着急,其实它就是一个基于 axios 封装的 react hooks 函数调用方法。让我来给你看一段示例(来自 axios-hooks README.md)
// React
import useAxios from 'axios-hooks'
function App() {
const [{ data, loading, error }, refetch] = useAxios(
'https://reqres.in/api/users?delay=1'
)
if (loading) return <p>Loading...</p>
if (error) return <p>Error!</p>
return (
<div>
<button onClick={refetch}>refetch</button>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
通过一个 useAxios 传递接口地址,然后返回接口的 data、loading、error 响应式状态值,由于是响应式的所以能够做到数据变更优雅的刷新页面,再也不用在各种生命周期函数 await 数据然后 setState 了,接口调用超级优雅。
这么优雅的方式怎么能只由 React 独享呢,自从用上 Vue3 的 TSX + 组合式 API 之后腿不酸了腰也不疼了(bushi),那我大 Vue 也必须有,当我有这个想法的时候那肯定有人比我更早想到,果然找了一下发现 VueUse 都已经给我端上来了 @vueuse/integrations/useAxios 这波得表扬一下 @Anthony Fu。我这里也同样贴一下代码(具体代码我就不解释了,毕竟和上面十分有八分神似)
// Vue
import { defineComponent } from 'vue';
import { useAxios } from '@vueuse/integrations/useAxios';
export default defineComponent({
setup(props, ctx) {
const { data, isLoading, error, execute } = useAxios('https://reqres.in/api/users?delay=1');
return () => {
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error!</p>;
return (
<div>
<button onClick={(e) => execute()}>refetch</button>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
};
},
});
从简单的使用角度来说确实已经和 axios-hooks 很接近了,但是(我要说但是了)在一个站点需要对接多个后端的时候,需要初始化多个 axios 实例在不同页面调用不同后端的接口,这个时候传递参数就会比较麻烦,比如需要在每个页面导入以及传递一个 instance 参数
// Vue
import axios from 'axios'
import { useAxios } from '@vueuse/integrations/useAxios';
const instance = axios.create({
baseURL: 'http://127.0.0.1:8081',
});
const instanceDefault = axios.create({
baseURL: 'http://127.0.0.1:8082',
});
const a1 = useAxios('/hello1', instance);
const a2 = useAxios('/hello2', instanceDefault);
而在 axios-hooks 中是有一个 makeUseAxios 能够创建多实例的,用多实例的好处就是我们不需要将 instance 在页面之导来导去。
// React
import axios from 'axios'
import { makeUseAxios } from 'axios-hooks'
const useAxiosAPIv1 = makeUseAxios({
axios: axios.create({ baseURL: 'http://127.0.0.1:8081' })
})
const useAxiosAPIv2 = makeUseAxios({
axios: axios.create({ baseURL: 'http://127.0.0.1:8082' })
})
const […] = useAxiosAPIv1("/posts")
const […] = useAxiosAPIv2("/users")
虽然说 @vueuse/integrations 没支持这种创建多实例 useAxios 方法,但是我们可以暴改它让他支持上这种方式呀,我先贴一下我的实现然后再详细介绍一下其中的逻辑(当然,我这里有很多实现也是参 axios-hooks 来简单实现的,可能会存在不少的缺陷)
// Vue
// file: useAxios.ts
import axios, { type AxiosInstance, type AxiosResponse } from 'axios';
import { useAxios as useAxiosHook } from '@vueuse/integrations/useAxios';
type ConfigureUseAxiosOption = {
instance?: AxiosInstance;
};
function makeUseAxios(option?: ConfigureUseAxiosOption) {
let defaultInstance = option?.instance ?? axios;
function useAxios<T = any, R = AxiosResponse<T>, D = any>(...args: any[]) {
if (args.length > 0) {
if (args.length == 1) {
// Example: useAxios("/api") -> useAxios("/api", defaultInstance)
// function useAxios<T = any, R = AxiosResponse<T>, D = any>(instance?: AxiosInstance): EasyUseAxiosReturn<T, R, D> & Promise<EasyUseAxiosReturn<T, R, D>>;
// function useAxios<T = any, R = AxiosResponse<T>, D = any>(config?: AxiosRequestConfig<D>): EasyUseAxiosReturn<T, R, D> & Promise<EasyUseAxiosReturn<T, R, D>>;
if (typeof args[0] !== 'function') {
args[1] = defaultInstance;
}
} else {
if (typeof args[1] !== 'function') {
if (typeof args[0] == 'string') {
if (args.length >= 3) {
if (typeof args[2] !== 'function') {
// Example: useAxios("/api", config, options) -> useAxios("/api", config, instance, options)
// function useAxios<T = any, R = AxiosResponse<T>, D = any, O extends UseAxiosOptionsWithInitialData<T> = UseAxiosOptionsWithInitialData<T>>(url: string, config?: AxiosRequestConfig<D>, options?: O): StrictUseAxiosReturn<T, R, D, O> & Promise<StrictUseAxiosReturn<T, R, D, O>>;
// function useAxios<T = any, R = AxiosResponse<T>, D = any, O extends UseAxiosOptionsBase<T> = UseAxiosOptionsBase<T>>(url: string, config?: AxiosRequestConfig<D>, options?: O): StrictUseAxiosReturn<T, R, D, O> & Promise<StrictUseAxiosReturn<T, R, D, O>>;
const options = args[2];
args[2] = defaultInstance;
args[3] = options;
}
// Example: useAxios("/api", config, instance, options) -> useAxios("/api", config, instance, options)
// function useAxios<T = any, R = AxiosResponse<T>, D = any, O extends UseAxiosOptionsWithInitialData<T> = UseAxiosOptionsWithInitialData<T>>(url: string, config: AxiosRequestConfig<D>, instance: AxiosInstance, options?: O): StrictUseAxiosReturn<T, R, D, O> & Promise<StrictUseAxiosReturn<T, R, D, O>>;
// function useAxios<T = any, R = AxiosResponse<T>, D = any, O extends UseAxiosOptionsBase<T> = UseAxiosOptionsBase<T>>(url: string, config: AxiosRequestConfig<D>, instance: AxiosInstance, options?: O): StrictUseAxiosReturn<T, R, D, O> & Promise<StrictUseAxiosReturn<T, R, D, O>>;
} else if (args.length === 2) {
// Example: useAxios("/api", config) -> useAxios("/api", config, instance)
args[2] = defaultInstance;
}
}
}
// 用户手动指定 instance 无须替换默认实例
// Example: useAxios("/api", instance);
// function useAxios<T = any, R = AxiosResponse<T>, D = any, O extends UseAxiosOptionsWithInitialData<T> = UseAxiosOptionsWithInitialData<T>>(url: string, instance?: AxiosInstance, options?: O): StrictUseAxiosReturn<T, R, D, O> & Promise<StrictUseAxiosReturn<T, R, D, O>>;
// function useAxios<T = any, R = AxiosResponse<T>, D = any, O extends UseAxiosOptionsBase<T> = UseAxiosOptionsBase<T>>(url: string, instance?: AxiosInstance, options?: O): StrictUseAxiosReturn<T, R, D, O> & Promise<StrictUseAxiosReturn<T, R, D, O>>;
// function useAxios<T = any, R = AxiosResponse<T>, D = any>(config?: AxiosRequestConfig<D>, instance?: AxiosInstance): EasyUseAxiosReturn<T, R, D> & Promise<EasyUseAxiosReturn<T, R, D>>;
}
}
// deprecated: 最初偷懒版本实现
// if (args.length < 2) args[1] = {}; // set AxiosRequestConfig<D>
// if (args.length < 3) args[2] = defaultInstance;
return useAxiosHook<T, R, D>(...args);
}
function configure(option?: ConfigureUseAxiosOption) {
if (option?.instance) defaultInstance = option.instance;
}
return Object.assign(useAxios as typeof useAxiosHook, {
configure,
});
}
const useAxios = makeUseAxios();
const configure = useAxios.configure;
export default useAxios;
export { useAxios, makeUseAxios, configure };
我们这里也实现了一个 makeUseAxios 函数接受了一个可选的配置项里面可传递一个默认 axios 实例,在 makeUseAxios 函数中我们定义了两个函数 useAxios 和 configure,其中 useAxios 是参考 @vueuse/integrations/useAxios 定义的重写包括泛型和入参以及返回数据的类型,另外就是 configure 函数,其实现就是利用闭包特性修改 defaultInstance 变量,最后通过 Object.assign 将 useAxios 函数以及 configure 函数返回了。
其实在看 axios-hooks 源代码的时候让我感到惊喜的是利用 Object.assign 实现的在函数上挂 configure 函数的操作,如果你对 Object.assign 不是很了解的话,我下面这段代码可能能够帮助你更简单的理解
const callUseX = function () {
console.log('call', callUseX);
};
const configX = function () {
console.log('call configX', configX);
};
callUseX['configX'] = configX;
callUseX();
callUseX.configX();
是的,在上面的代码中 callUseX 既可以当作函数直接调用,又可以当作“对象”调用其包含的函数。这个时候你再去理解 在 JavaScript 中,每个函数实际上都是一个 Function 对象 就很具体了。
再说回在 Vue3 中使用 makeUseAxios 创建多 useAxios 实例的调用方式,我这里简单写了一个测试用例
// Vue
import { useAxios as useAxiosHook, type UseAxiosOptionsBase } from '@vueuse/integrations/useAxios';
import axios, { type AxiosRequestConfig } from 'axios';
import { makeUseAxios } from './useAxios';
const instance = axios.create({
baseURL: 'http://127.0.0.1:8081',
withCredentials: false,
});
const instanceDefault = axios.create({
baseURL: 'http://127.0.0.1:8082',
withCredentials: false,
});
// use instanceDefault
const useAxios = makeUseAxios({
instance: instanceDefault,
});
const option: UseAxiosOptionsBase = {
immediate: true,
};
const config: AxiosRequestConfig = {};
const a1 = useAxios('/hello1', config); // url -> http://127.0.0.1:8082/hello1
const a2 = useAxios('/hello2', instance); // url -> http://127.0.0.1:8081/hello2
const a3 = useAxios('/hello3', config, instance); // url -> http://127.0.0.1:8081/hello3
const a4 = useAxios('/hello4', config, instance, option); // url -> http://127.0.0.1:8081/hello4
const a5 = useAxios('/hello5', config, option); // url -> http://127.0.0.1:8082/hello5
const a6 = useAxiosHook('/hello6', config, option); // url -> /hello6
const a7 = useAxios('/hello7', config, axios, option); // url -> /hello7
const a8 = useAxios('/hello8', instance); // url -> http://127.0.0.1:8081/hello8
好了,恭喜你又学到一个小技巧,记得点赞、转发、评论三连,我们下次见。