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

war*_*990 10 javascript reactjs react-hooks

我是 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
Run Code Online (Sandbox Code Playgroud)

经常被另一个组件使用getUserRole,例如

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

...

const auth = useAuth();
UserService.getUserRole(auth.user?.access_token);
Run Code Online (Sandbox Code Playgroud)

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

Emi*_*ron 12

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

\n

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

\n

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

\n
import \xc2\xad{ useState, useEffect } from "react";\nimport { useAuth } from "react-oidc-context";\nimport UserService from "../../../services/authentication/userService";\n\n/**\n * Custom hooks that fetches the roles for the logged in user.\n */\nconst useRoles = () => {\n  const auth = useAuth();\n  const [roles, setRoles] = useState();\n\n  useEffect(() => {\n    if (!user) return; // pre-condition\n    UserService\n      .getUserRole(auth.user.access_token)\n      .then(setRoles);\n  }, [auth.user]);\n\n  return roles;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后在任意组件中:

\n
import useRoles from "../useRoles";\n\nconst MyExampleComponent = () => {\n  const roles = useRoles();\n\n  if (!roles) return <span>Please login (or something) to see the roles!</span>\n\n  return <div>{/* use roles here */}</div>\n}\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n

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

\n
// I also made it a class, but it would also work with an object.\nclass UserService {\n  constructor(axios) {\n    this.axios = axios;\n  }\n\n  getUserRole(){\n    // use the local axios instance\n    return this.axios({\n      method: "get",\n      // Use the default URL from local axios instance \n      url: "user/role",\n    })\n      .then(({ data }) => data)\n      .catch(console.log),\n  }\n\n  getSomethingElse() {\n    // ...\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
// UserServiceContext.js\nimport React from \'react\';\nimport { useAuth } from "react-oidc-context";\nimport UserService from "../../../services/authentication/userService";\n\n// Local axios instance\nconst axiosInstance = axios.create({\n  baseURL: \'https://<url>\', // set the base URL once here\n});\n\nconst userServiceInstance = new UserService(axiosInstance);\n\nconst UserServiceContext = React.createContext(userServiceInstance);\n\n// Convenience hook\nexport const useUserService = () => useContext(UserServiceContext);\n\nexport const UserServiceProvider = (props) => {\n  const auth = useAuth();\n\n  useEffect(() => {\n    // If the user changes, update the token used by our local axios instance.\n    axiosInstance.defaults.headers\n      .common[\'Authorization\'] = `Bearer ${auth.user?.access_token}`;\n  }, [auth.user]);\n\n  return (\n    <UserServiceContext.Provider value={userServiceInstance} {...props} />\n  );  \n}\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
import { AuthProvider } from "react-oidc-context";\nimport { UserServiceProvider } from "./UserServiceContext";\n\nconst App = () => (\n  <AuthProvider>\n    <UserServiceProvider>\n      <Content />\n    </UserServiceProvider>\n  </AuthProvider>\n);\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
import { useUserService } from \'../UserServiceContext\';\n\nconst MyExampleComponent = () => {\n  const userService = useUserService();\n  const [roles, setRoles] = useState();\n\n  // e.g. load roles once on mount.\n  useEffect(() => {\n    userService // use the service from the context\n      .getUserRole() // no auth token needed anymore!\n      .then(setRoles);\n  }, []);\n\n  if (!roles) return <span>Please login (or something) to see the roles!</span>\n\n  return <div>{/* use roles here */}</div>\n}\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
// Here\'s what the hook could look like if it used the new provider above.\nconst useRoles = () => {\n  const userService = useUserService();\n  const [roles, setRoles] = useState();\n\n  // e.g. load roles once on mount.\n  useEffect(() => {\n    userService // use the service from the context\n      .getUserRole() // no auth token needed anymore!\n      .then(setRoles);\n  }, []);\n\n  return roles;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
\n

免责声明:我使用了最少的代码来演示可行的解决方案,我的答案可能无法解决您情况的所有限制。例如, axios 实例可以在提供者内部创建为延迟初始化useRef,实例也是如此UserService,等等。

\n

  • 这里的关键要点是,您仍然可以在利用 React 功能的同时解耦逻辑。自从 Hooks 出现以来,我几乎只见过一种“all-in”方法,其中所有服务/应用程序逻辑都通过自定义 hooks/providers 耦合到 React。这样的示例证明您可以从其他技术(Angular 在这里非常合适)*迁移到*React,并且也可以轻松地从 React 迁移*await*。 (2认同)