javascript 在非React组件内部使用钩子的替代方案是什么?

vsnjm48y  于 2023-01-11  发布在  Java
关注(0)|答案(1)|浏览(94)

我是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);

正如您所看到的,我必须不断地从useAuth传递access_token。有没有什么方法可以在UserService中调用useAuth,这样我就不必不断地从组件传递access_token

brgchamk

brgchamk1#

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

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

如果角色到处都在使用,那么一个快速的自定义钩子会让你开始,这是 Package 自定义逻辑的最简单的方法,因为钩子是用来 Package 有状态逻辑以便在组件中重用的。

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>
}

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

如果用户服务上有很多不同的方法需要在整个应用程序中使用,那么在我看来, Package 整个服务并通过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";

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

const userServiceInstance = new UserService(axiosInstance);

const UserServiceContext = React.createContext(userServiceInstance);

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

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'] = `Bearer ${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>
}

请注意,定制钩子仍然可以用来 Package 角色获取逻辑,上下文和钩子可以一起使用来 Package 逻辑到各自的首选项。

// 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示例可以在提供程序中创建为延迟初始化的useRefUserService示例也是如此,等等。*

相关问题