在 Vue 3 Typescript 中使用提供/注入访问组件中的方法,错误对象可能是“未定义”

Posted

技术标签:

【中文标题】在 Vue 3 Typescript 中使用提供/注入访问组件中的方法,错误对象可能是“未定义”【英文标题】:Accessing method in component with Provide/Inject in Vue 3 Typescript, Error Object is possibly 'undefined' 【发布时间】:2022-01-14 05:44:53 【问题描述】:

为什么我不能在 header.vue 组件中使用注入调用loginWithRedirect? (Vue 3,打字稿)

src/components/global/HeaderMenu.vue:77:17 TS2339 中的错误:属性 “Auth0Plugin”类型上不存在“loginWithRedirect”。 75 |方法: 76 |登录()

77 | this.auth.loginWithRedirect(); | ^^^^^^^^^^^^^^^^^ 78 | , 79 |登出() 80 | // this.$AuthPlugin.logout();

我是 typescript 的新手,有一个关于正确使用 inject 的问题(在 typescript 和 Vue 3 的上下文中。)最初的问题得到了回答 here。据我了解,我做了以下事情。

Main.js .provide('authPlugin', AuthPlugin)

​async function init() 
     ​const AuthPlugin = await Auth0.init(
       ​onRedirectCallback: (appState) => 
         ​router.push(appState && appState.targetUrl ? appState.targetUrl : window.location.pathname);
       ​,
       ​clientId: 'xxxx',
       ​domain: 'xxxx',
       ​audience: 'xxxx'
     ​);
     ​const app = createApp(App);
     ​// library.add(faLink, faUser, faPowerOff);
     ​app
       ​.use(AuthPlugin)
       ​// .use(store)
       ​.use(router)
       ​// Make BootstrapVue available throughout project
       ​.use(BootstrapVue3)
   ​
       ​.provide('authPlugin', AuthPlugin)
       ​// .component("font-awesome-icon", FontAwesomeIcon)
       ​.mount('#app');
   ​
   ​
   ​init();

在我的header.vue 组件中,我想使用来自 auth index.ts 的loginWithRedirect。为方便起见,我在 components/header.vue... 中添加了以下内容...

导入并设置类型

   ​import  defineComponent  from 'vue';
   ​import  inject  from 'vue';
   ​import  Auth0  from '@/auth';
   ​export type TAuthPlugin = typeof Auth0;

​在同一个header.vue组件的 setup()方法中...

   ​setup() 
       ​const auth = inject<TAuthPlugin>('Auth');  

这似乎让我可以通过this.auth 访问方法login

但是,如果我尝试访问 this.auth.login,我会得到一个。错误,为什么?

src/components/global/HeaderMenu.vue:77:7
TS2532: Object is possibly 'undefined'.
    75 |   methods: 
    76 |     login() 
  > 77 |       this.auth.loginWithRedirect();
       |       ^^^^^^^^^
    78 |     ,
    79 |     logout() 
    80 |       // this.$AuthPlugin.logout();

header.vue

<script lang="ts">
import  defineComponent  from 'vue';
import  inject  from 'vue';
import  Auth0  from '@/auth';
export type TAuthPlugin = typeof Auth0;
export default defineComponent(
  name: 'HeaderMenu',
  inject: ['authPlugin'],
  methods: 
    login() 
      this.auth.loginWithRedirect();
    ,
    logout() 
      // this.$AuthPlugin.logout();
      this.$router.push( path: '/' );
    ,
  ,
  setup() 
    const auth = inject<TAuthPlugin>('Auth');
    // console.log(auth.loginWithRedirect);
    // /* eslint-disable */

    return 
      auth,
    ;
  ,
);
</script> 

auth index.ts 供参考。

import createAuth0Client, 
  Auth0Client,
  GetIdTokenClaimsOptions,
  GetTokenSilentlyOptions,
  GetTokenWithPopupOptions,
  LogoutOptions,
  RedirectLoginOptions,
  User,
 from '@auth0/auth0-spa-js';
import  App, Plugin, computed, reactive, watchEffect  from 'vue';
import  NavigationGuardWithThis  from 'vue-router';

let client: Auth0Client;

interface Auth0PluginState 
  loading: boolean;
  isAuthenticated: boolean;
  user: User | undefined;
  popupOpen: boolean;
  error: any;


const state = reactive<Auth0PluginState>(
  loading: true,
  isAuthenticated: false,
  user: ,
  popupOpen: false,
  error: null,
);

async function handleRedirectCallback() 
  state.loading = true;

  try 
    await client.handleRedirectCallback();
    state.user = await client.getUser();
    state.isAuthenticated = true;
   catch (e) 
    state.error = e;
   finally 
    state.loading = false;
  


function loginWithRedirect(o: RedirectLoginOptions) 
  return client.loginWithRedirect(o);


function getIdTokenClaims(o: GetIdTokenClaimsOptions) 
  return client.getIdTokenClaims(o);


function getTokenSilently(o: GetTokenSilentlyOptions) 
  return client.getTokenSilently(o);


function getTokenWithPopup(o: GetTokenWithPopupOptions) 
  return client.getTokenWithPopup(o);


function logout(o: LogoutOptions) 
  return client.logout(o);


const authPlugin = 
  isAuthenticated: computed(() => state.isAuthenticated),
  loading: computed(() => state.loading),
  user: computed(() => state.user),
  getIdTokenClaims,
  getTokenSilently,
  getTokenWithPopup,
  handleRedirectCallback,
  loginWithRedirect,
  logout,
;

const routeGuard: NavigationGuardWithThis<undefined> = (to: any, from: any, next: any) => 
  const  isAuthenticated, loading, loginWithRedirect  = authPlugin;

  const verify = async () => 
    // If the user is authenticated, continue with the route
    if (isAuthenticated.value) 
      return next();
    

    // Otherwise, log in
    await loginWithRedirect( appState:  targetUrl: to.fullPath  );
  ;

  // If loading has already finished, check our auth state using `fn()`
  if (!loading.value) 
    return verify();
  

  // Watch for the loading property to change before we check isAuthenticated
  watchEffect(() => 
    if (!loading.value) 
      return verify();
    
  );
;

interface Auth0PluginOptions 
  domain: string;
  clientId: string;
  audience: string;
  redirectUri: string;

  onRedirectCallback(appState: any): void;


async function init(options: Auth0PluginOptions): Promise<Plugin> 
  client = await createAuth0Client(
    // domain: process.env.VUE_APP_AUTH0_DOMAIN,
    // client_id: process.env.VUE_APP_AUTH0_CLIENT_KEY,
    domain: options.domain,
    client_id: options.clientId,
    audience: options.audience,
    redirect_uri: options.redirectUri,
  );

  try 
    // If the user is returning to the app after authentication
    if (window.location.search.includes('code=') && window.location.search.includes('state=')) 
      // handle the redirect and retrieve tokens
      const  appState  = await client.handleRedirectCallback();

      // Notify subscribers that the redirect callback has happened, passing the appState
      // (useful for retrieving any pre-authentication state)
      options.onRedirectCallback(appState);
    
   catch (e) 
    state.error = e;
   finally 
    // Initialize our internal authentication state
    state.isAuthenticated = await client.isAuthenticated();
    state.user = await client.getUser();
    state.loading = false;
  

  // return 
  //   install: (app: App<Element>) => 
  //     app.provide('Auth', authPlugin);
  //   ,

  return 
    install: (app: App) => 
      app.provide('Auth', authPlugin);
    ,
  ;


interface Auth0Plugin 
  init(options: Auth0PluginOptions): Promise<Plugin>;
  routeGuard: NavigationGuardWithThis<undefined>;


export const Auth0: Auth0Plugin = 
  init,
  routeGuard,
;

​1:Vue 3 with Typescript inject does not work as intended. Spread types may only be created from object types

【问题讨论】:

我没有了解这里的 auth0 api 会发生什么,但是 authPluginAuth0 是如何相关的?它们显然是不同的对象。如果您希望注入authPlugin,请不要使用typeof Auth0 键入它。请注意,上一个问题的答案说您需要typeof authPlugin Auth0 和 authPlugin 应该是同一个对象,或者至少这是意图。我只想在我的组件中访问来自 Auth0 的方法 看起来我不需要在 main.ts 中提供,因为我从 auth 获得了一个实例,我可以看到它是... ``` return install: (app: App) => app.provide('Auth', authPlugin); , ;``` 我不确定意图,因为它们显然不一样。请参阅代码中的 const authPluginconst Auth0 声明。 “看起来我不需要在 main.ts 中提供” - 是的,你不需要它,你想直接导入它而不是使用插件,你可以不使用提供/注入 我的意思是提供/注入适用于特定目标,例如松散耦合或深度 DI。出于这个原因,它在第三方插件中使用,但在您自己的应用程序中,您可能根本不需要插件。只需从 auth 模块中导入 authPlugin(或任何你称之为的),无论你在哪里使用它。 this.$AuthPlugin - 这是一种处理全局依赖关系的过时方法,主要是由于缺少模块造成的,而现代 TS 环境中并非如此 【参考方案1】:

问题在于authPluginAuth0 是不相关的对象。

应该是provide('authPlugin', authPlugin)inject&lt;typeof authPlugin&gt;('authPlugin'),或者provide('authPlugin', Auth0)inject&lt;typeof Auth0&gt;('authPlugin')

在这种情况下,inject 甚至不需要。它常用于第三方插件中的松耦合,也可用于嵌套组件中的深度依赖注入。除非应用程序设计要求 auth 是一个单独的包,否则它不需要是插件和provide/inject。由于authPlugin 是在评估模块时定义的,因此可以只从auth 模块中导出它并在使用它的地方导入。

【讨论】:

以上是关于在 Vue 3 Typescript 中使用提供/注入访问组件中的方法,错误对象可能是“未定义”的主要内容,如果未能解决你的问题,请参考以下文章

Vue 3系列之03——Vue 3使用TypeScript

Vue 3系列之03——Vue 3使用TypeScript

在Vue 3中使用Typescript和Jsx

vue之--使用TypeScript

如何使用 Typescript 在 Vue 3.0 setup() 函数中使用 async/await

vue-type-check: Vue 模板中的 Typescript 类型检查