在非 React 组件中使用钩子的替代方法是什么?

IT技术 javascript reactjs react-hooks
2021-04-28 17:54:58

我是 React 的新手,我有这个功能。

    import Axios from "axios";
    
    const UserService = {
        getUserRole: (access_token: string = "") => {
            return Axios({
                method: "get",
                url: "https://<url>/user/role",
                headers: {
                    "Authorization": `Bearer ${access_token}`
                }
            }).then((response) => {
                return response.data;
            }).catch((error) => {
                console.log(error);
            });
        }
    }

export default UserService

getUserRole是由另一个组件不断使用,例如

import UserService from "../../../services/authentication/userService";
import { useAuth } from "react-oidc-context";

...

const auth = useAuth();
UserService.getUserRole(auth.user?.access_token);

如您所见,我必须不断地传递access_tokenfrom useAuth有什么方法可以useAuth在我的内部调用UserService这样我就不必经常access_token从我的组件中传递

1个回答

这个问题的前提是落后的,因为我们不应该尝试在 React 之外使用钩子,而是在 React 内部使用外部代码。

快速解决方案:自定义挂钩

如果角色在所有地方都使用,快速自定义挂钩将帮助您入门。这是包装自定义逻辑的最简单方法,因为钩子旨在包装有状态的逻辑以便在组件中重用。

import ­{ useState, useEffect } from "react";
import { useAuth } from "react-oidc-context";
import UserService from "../../../services/authentication/userService";

/**
 * Custom hooks that fetches the roles for the logged in user.
 */
const useRoles = () => {
  const auth = useAuth();
  const [roles, setRoles] = useState();

  useEffect(() => {
    if (!user) return; // pre-condition
    UserService
      .getUserRole(auth.user.access_token)
      .then(setRoles);
  }, [auth.user]);

  return roles;
}

然后在任何组件中:

import useRoles from "../useRoles";

const MyExampleComponent = () => {
  const roles = useRoles();

  if (!roles) return <span>Please login (or something) to see the roles!</span>

  return <div>{/* use roles here */}</div>
}

更好的解决方案:服务提供商

如果用户服务上有很多不同的方法需要在整个应用程序中使用,那么在我看来,包装整个服务并通过React 的上下文提供一个现成的版本是最好的。

但首先,让我们UserService稍微修改一下,使其使用本地 axios 实例而不是全局 axios 实例。

// I also made it a class, but it would also work with an object.
class UserService {
  constructor(axios) {
    this.axios = axios;
  }

  getUserRole(){
    // use the local axios instance
    return this.axios({
      method: "get",
      // Use the default URL from local axios instance 
      url: "user/role",
    })
      .then(({ data }) => data)
      .catch(console.log),
  }

  getSomethingElse() {
    // ...
  }
}

然后,我们可以为用户服务设置 React 的上下文。

// UserServiceContext.js
import React from 'react';
import { useAuth } from "react-oidc-context";
import UserService from "../../../services/authentication/userService";

const UserServiceContext = React.createContext(null);

// Convenience hook
export const useUserService = () => useContext(UserServiceContext);

// Local axios instance
const axiosInstance = axios.create({
  baseURL: 'https://<url>', // set the base URL once here
  headers: {
    "Authorization": `Bearer ${access_token}`
  }
});

const userServiceInstance = new UserService(axiosInstance);

export const UserServiceProvider = (props) => {
  const auth = useAuth();

  useEffect(() => {
    // If the user changes, update the token used by our local axios instance.
    axiosInstance.defaults.headers
      .common['Authorization'] = auth.user?.access_token;
  }, [auth.user]);

  return <UserServiceContext.Provider value={userServiceInstance} {...props} />;  
}

然后在任何地方,但通常在应用程序的根目录:

import { AuthProvider } from "react-oidc-context";
import { UserServiceProvider } from "./UserServiceContext";

const App = () => (
  <AuthProvider>
    <UserServiceProvider>
      <Content />
    </UserServiceProvider>
  </AuthProvider>
);

现在一切准备就绪,可以在任何组件中使用!

import { useUserService } from '../UserServiceContext';

const MyExampleComponent = () => {
  const userService = useUserService();
  const [roles, setRoles] = useState();

  // e.g. load roles once on mount.
  useEffect(() => {
    userService // use the service from the context
      .getUserRole() // no auth token needed anymore!
      .then(setRoles);
  }, []);

  if (!roles) return <span>Please login (or something) to see the roles!</span>

  return <div>{/* use roles here */}</div>
}

请注意,自定义挂钩仍可用于包装角色获取逻辑。上下文和钩子都可以一起使用来将逻辑包装到每个人自己的偏好中。

// Here's what the hook could look like if it used the new provider above.
const useRoles = () => {
  const userService = useUserService();
  const [roles, setRoles] = useState();

  // e.g. load roles once on mount.
  useEffect(() => {
    userService // use the service from the context
      .getUserRole() // no auth token needed anymore!
      .then(setRoles);
  }, []);

  return roles;
}

我认为提供者解决方案更好,因为它提供了更大的灵活性,同时保持对公开 API 的控制

在我的解决方案中,我建议使用UserService实例作为提供的值,但可以更改提供程序以仅公开 API 的一部分,或者它可以自动提供角色和其他数据。由你决定!


免责声明:我使用最少的代码来演示一个有效的解决方案,我的回答可能无法解决您的情况的所有限制。例如, axios 实例可以在提供者内创建useMemo,同样适用于UserService实例等。