【问题标题】:Using axios inside custom react hook在自定义反应钩子中使用 axios
【发布时间】:2020-07-18 09:34:50
【问题描述】:

我想创建一个带有拦截器的全局 axios 实例,该拦截器添加带有访问令牌的标头。 访问令牌存储在 cookie 中,但我不知道如何访问拦截器内部的 cookie,因为它不是反应组件。

我曾尝试在这样的自定义挂钩中创建 axios 实例:

import React, { useEffect, useState } from "react";
import { useCookies } from "react-cookie";
import axios from "axios";

function useApiClient() {
  const [cookies, setCookie] = useCookies(["token"]);
  const [client, setClient] = useState(null);

  useEffect(() => {
    setClient(
      axios
        .create({
          baseURL: "http://localhost:8080/api/",
          responseType: "json",
        })
        .interceptors.request.use(
          (config) => {
            console.log("setting up");
            if (cookies["token"] != null) {
              config.headers.authorization = cookies["token"];
            }
          },
          (error) => Promise.reject(error)
        )
    );
  }, [client]);

  return client;
}

export default useApiClient;

但是当我尝试使用以下方法调用它时:

const client = useApiClient();

function attemptLogin() {
    const credentials = {
      username: username,
      password: password,
    };

    client
      .post("/authenticate", JSON.stringify(credentials), {
         headers: {
          "Content-Type": "application/json",
         },
       }).then(....)

我得到以下错误:

TypeError: client.post 不是函数

谁能帮忙?

【问题讨论】:

  • 我注意到的一个问题是您需要在数组中像这样的客户端依赖项:[client] 在 useEffect 中。您还可以展示如何使用 client.post 吗?
  • 嗨@SuleymanSah 比你的评论。我曾尝试在数组中添加依赖项,但没有运气。我已经编辑了我的问题以显示我如何使用客户端。
  • 您是否考虑过 axios 配置的 withCredentials: true 选项?

标签: reactjs cookies axios


【解决方案1】:

问题是您正在尝试使用react-cookie,它提供了一个用于在组件中使用cookies 的api。在某些情况下可能没问题,但是对于需要检查每个请求的 cookie 的情况,它会强制将 axios 实例放在组件层次结构顶部的某个位置,并且也使得在组件之外使用实例变得更加困难(大多数您可能会将实例传递给外部函数)。

我的建议是在单独的模块中创建一个实例,并使用不同的工具来获取 cookie,例如 universal-cookie

// apiClient.js
import axios from "axios";
import Cookies from "universal-cookie";

const cookies = new Cookies();

export const apiClient = axios
  .create({
    baseURL: "http://localhost:8080/api/",
    responseType: "json"
  })
  .interceptors.request.use(
    config => {
      console.log("setting up");
      const token = cookies.get("token");
      if (token != null) {
        config.headers.authorization = token;
      }
    },
    error => Promise.reject(error)
  );

之后,您可以在应用程序的任何地方使用此实例

import React from 'react';
import { apiClient } from "./apiClient";

export default ({ name }) => {
  useEffect(() => {
    apiClient.get(/*...*/).then(/*...*/);
  });

  return <h1>Hello {name}!</h1>;
}

如果您更喜欢使用 hooks,您可以轻松地将客户端封装成一个:

// apiClient.js
export function useApiClient() { return apiClient; } 

Playgroung with full example

【讨论】:

    【解决方案2】:

    这可能是因为useApiClient.js 中的useEffect 是异步方法。

    我个人认为直接将标头分配给axios 对象并没有什么坏处。但是我看到您正在使用axios.create,所以我假设您可能正在调用多个服务 API/端点,并且都不需要相同的标头。总之……

    建议

    1. 使useApiClient 成为一个简单的类,它返回带有所需标题的axios 实例。如果没有,我严重错过了在这里使用useEffect 的意义。

    行为

    在您归档的每个文件中调用此方法,所有文件都将具有单独的 axios 实例,而不会污染全局 axios 对象。

    useApiClient.js

        function useApiClient {
            return axios
              .create({
                baseURL: "http://localhost:8080/api/",
                responseType: "json"
            })
            .interceptors.request.use(
              config => {
                console.log("setting up");
                const token = cookies.get("token");
                if (token != null) {
                  config.headers.authorization = token;
                }
              },
              error => Promise.reject(error)
            );
          }
        }
    
        export default useApiClient;
    
    1. 创建一个工厂类,它根据输入参数返回axios 实例(根据传递的参数分配不同的标头并返回实例)。如果可行,您也可以将其命名为 Singleton

    希望对你有帮助。

    【讨论】: