在整个 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初学者。

广告
将在 10 秒后关闭
bannerAds