在整个 React 项目中共享 axios 的配置
问题设定
我们将思考如何在整个React项目中共享axios的设置,针对不同的案例。
已实现的东西
我已经将实现的内容整理到了这里。
演示网站:
实施步骤
1. 通过 export 共享变量
axios 的公共配置可以通过 axios.defaults 进行设置。
axios.defaults.baseURL = 'https://api.waifu.pics/';
axios.defaults.headers.common.Authorization = `Bearer <authenticity-token>`;
如果您正在使用 ES 模块,则可以通过 export 共享这种在全局范围内被引用的变量。
import axios from 'axios';
const myAxios = axios.create();
myAxios.defaults.baseURL = 'https://api.waifu.pics/';
myAxios.defaults.headers.common.Authorization = `Bearer <authenticity-token>`;
// グローバルに使用される変数を他のファイルから読み込めるようにする
export default myAxios;
import myAxios from './MyAxios';
function MyComponent(): JSX.Element {
myAxios.get(...).then(...); // myAxios を使用した API 呼び出し
}
只需一种选择:将初始处理写入顶层作用域,并在文件开头进行导入,可以确保首先执行myAxios的初始化。如果设置是固定值,则此实现没有问题。
或者, 事实上,除非通过create()函数创建axios对象,否则只会存在一个对象,因此只需要在index.tsx或App.tsx中设置初始值即可。
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
+ import axios from 'axios';
+
+ axios.defaults.baseURL = 'https://api.waifu.pics/';
+ axios.defaults.headers.common.Authorization = `Bearer <authenticity-token>`;
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
在函数组件内进行初始化。
考虑将此初始化处理程序编写在函数组件内部。例如,如果使用useCookies,并且要使用cookies.token的值来初始化axios.defaults.headers.common.Authorization的值,则需要在函数组件内部实现初始化处理,而不是在顶层范围。为此,我们引入一个中间组件AxiosProvider。
+ import AxiosProvider from './components/AxiosProvider'
root.render(
<React.StrictMode>
- <App />
+ <AxiosProvider>
+ <App />
+ </AxiosProvider>
</React.StrictMode>
);
import axios from 'axios';
function AxiosProvider(props: { children: React.ReactNode }): JSX.Element {
// 例えば、useCookies を使用する必要があるかもしれない
// const [cookies, ..., ...] = useCookies(...);
// const authenticity_token = ...;
axios.defaults.baseURL = 'https://api.waifu.pics/';
axios.defaults.headers.common.Authorization = `Barer <authenticity_token>`;
return (
<>
{props.children}
</>
);
}
export default AxiosProvider;
API 调用应写在 useEffect 中。
import axios from 'axios';
function MyComponent(): JSX.Element {
- axios.get(...).then(...); // axios を使った API 呼び出し
+ // useEffect は全コンポーネントのレンダリングが完了した後に呼び出される
+ useEffect(() => {
+ axios.get(...).then(...); // axios を使った API 呼び出し
+ }, []);
}
只需提供一种选择:
通过这种方式编写代码,我们可以确保在初始化处理完成之后执行 useEffect 内部的 API 调用。
使用 useContext
那么,这种方法的问题在于,即使变量在初始加载完成后发生了变化,React也无法检测到它,因此无法重新渲染组件。结果是,显示在屏幕上的信息仍然是旧的。
如果将变量作为 prop 传递,当然可以将更改传递给子组件,但在这种情况下,代码可能会变得冗长。
通常,可以使用 useContext 来在 React 中共享这样的全局变量。
import React from 'react';
import axios from 'axios';
// 引数に初期値 axios を与えているが、実際にはこの値は参照されない。
const AxiosContext = React.createContext(axios);
export default AxiosContext;
import axios from 'axios';
import AxiosContext from './AxiosContext';
let myAxios = axios.create();
let isAxiosDirty = true;
function AxiosProvider(props: { children: React.ReactNode }): JSX.Element {
// 例えば、useCookies を使用する必要があるかもしれない
// const [cookies, ..., ...] = useCookies(...);
// const authenticity_token = ...;
if (isAxiosDirty) {
isAxiosDirty = false;
myAxios = axios.create();
myAxios.defaults.baseURL = ...;
myAxios.defaults.headers.common.Authorization = ...;
}
return (
<AxiosContext.Provider value={myAxios}>
{props.children}
</AxiosContext.Provider>
);
}
export default AxiosProvider;
按照以上的写法,您可以在App以下的任意组件内引用axios。
import AxiosContext from './AxiosContext';
import { useEffect, useContext } from 'react';
function MyComponent(): JSX.Element {
// myAxios に変更が生じるとコンポーネントが再レンダリングされる
const myAxios = useContext(AxiosContext);
useEffect(() => {
myAxios.get(...).then(...); // myAxios を使った処理
}, [myAxios]);
}
正如之前所述,使用useContext的最大优点是能够检测到axios值的变化并重新渲染组件。
4. 可以在不同步的情况下,或在 useEffect 中进行初始化。
好吧,到目前为止的实现大多数情况下没有问题。然而,有时候需要根据某种异步处理的结果来初始化 axios,或者因某种原因需要在 useEffect 中包裹初始化处理。这让我头疼不已。
let myAxios = axios.create();
function AxiosProvider(props: { children: JSX.Element }): JSX.Element {
...
// 例えば useEffect で囲む必要があったり
useEffect(() => {
// または非同期で処理する必要があるかもしれない
someProcess().then((someData) => {
myAxios = axios.create();
myAxios.defaults.baseURL = ...;
myAxios.defaults.headers.common.Authorization = ...;
});
}, [someData]);
return ...;
}
即使是在这种情况下,您仍然可以像之前一样使用useContext来检测值的更改并重新渲染函数组件,然后使用useEffect来再次执行API调用。
import AxiosContext from './AxiosContext';
function MyComponent(): JSX.Element {
// axios の値が更新された場合にも MyComponent() が呼び出されるようになる
const axios = useContext(AxiosContext);
// axios の値が変わっていた場合のみ、API 呼び出しを実行する
useEffect(() => {
// axios を使った API 呼び出し
axios.get(...).then(...);
}, [axios]);
}
然而,按照这种写法,axios 在正常初始化之前会先执行一次 useEffect。根据配置的内容,可能会发生运行时错误。因此,我们考虑将 axios 初始化为 null 的方法。
+ import type { AxiosStatic } from 'axios';
+
- let myAxios = axios.create();
+ let myAxios: AxiosStatic | null = null;
function AxiosProvider(props: { children: JSX.Element }): JSX.Element {
...
}
function MyComponent(): JSX.Element {
const axios = useContext(AxiosContext);
useEffect(() => {
- axios.get(...).then(...);
+ if (axios !== null) {
+ axios.get(...).then(...);
+ }
}, [axios]);
}
这种写法非常接近最佳方式,但是if语句感觉稍微有些冗长。
5. 引入AxiosMock并避免使用if语句。
为了避免使用条件句,我们引入 AxiosMock。AxiosMock是一个用作axios未初始化时的模拟对象的工具。
class AxiosMock {
get(url: string): AxiosMock {
return this;
}
post(url: string): AxiosMock {
return this;
}
delete(url: string): AxiosMock {
return this;
}
patch(url: string): AxiosMock {
return this;
}
then(callback: (res: { data: any }) => void): AxiosMock {
return this;
}
catch(callback: (error: any) => void): AxiosMock {
return this;
}
}
export default AxiosMock;
- let myAxios: AxiosStatic | null = null;
+ let myAxios: AxiosStatic | AxiosMock = new AxiosMock();
function AxiosProvider(props: { children: JSX.Element }): JSX.Element {
...
}
这样就不再需要进行 null 检查了。
function MyComponent(): JSX.Element {
const axios = useContext(AxiosContext);
useEffect(() => {
axios.get(...).then(...);
}, [axios]);
}
以上结束,辛苦了!
最后
我可以用这种方式来配置axios。
如果有其他更好的写法,欢迎轻松评论,因为我是React初学者。