在 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 会发生什么,但是authPlugin
和 Auth0
是如何相关的?它们显然是不同的对象。如果您希望注入authPlugin
,请不要使用typeof Auth0
键入它。请注意,上一个问题的答案说您需要typeof authPlugin
Auth0 和 authPlugin 应该是同一个对象,或者至少这是意图。我只想在我的组件中访问来自 Auth0 的方法
看起来我不需要在 main.ts 中提供,因为我从 auth 获得了一个实例,我可以看到它是... ``` return install: (app: App) => app.provide('Auth', authPlugin); , ;```
我不确定意图,因为它们显然不一样。请参阅代码中的 const authPlugin
和 const Auth0
声明。 “看起来我不需要在 main.ts 中提供” - 是的,你不需要它,你想直接导入它而不是使用插件,你可以不使用提供/注入
我的意思是提供/注入适用于特定目标,例如松散耦合或深度 DI。出于这个原因,它在第三方插件中使用,但在您自己的应用程序中,您可能根本不需要插件。只需从 auth 模块中导入 authPlugin
(或任何你称之为的),无论你在哪里使用它。 this.$AuthPlugin
- 这是一种处理全局依赖关系的过时方法,主要是由于缺少模块造成的,而现代 TS 环境中并非如此
【参考方案1】:
问题在于authPlugin
和Auth0
是不相关的对象。
应该是provide('authPlugin', authPlugin)
和inject<typeof authPlugin>('authPlugin')
,或者provide('authPlugin', Auth0)
和inject<typeof Auth0>('authPlugin')
。
在这种情况下,inject
甚至不需要。它常用于第三方插件中的松耦合,也可用于嵌套组件中的深度依赖注入。除非应用程序设计要求 auth 是一个单独的包,否则它不需要是插件和provide
/inject
。由于authPlugin
是在评估模块时定义的,因此可以只从auth
模块中导出它并在使用它的地方导入。
【讨论】:
以上是关于在 Vue 3 Typescript 中使用提供/注入访问组件中的方法,错误对象可能是“未定义”的主要内容,如果未能解决你的问题,请参考以下文章