Vue 3 + TypeScript:使用 JS 扩展运算符从 setup() 返回的对象在 vscode 中引发错误
Posted
技术标签:
【中文标题】Vue 3 + TypeScript:使用 JS 扩展运算符从 setup() 返回的对象在 vscode 中引发错误【英文标题】:Vue 3 + TypeScript: Objects returned from setup() using the JS spread operator throw an error in vscode 【发布时间】:2021-12-07 19:56:08 【问题描述】:我有一个简单的Vue 3 + TypeScript repo,我正在尝试集成一个 Auth0 插件。
它在前端显示字符串化的user
对象,并且按预期工作。
但 Visual Studio Code 显示 TypeScript 错误 Cannot find name 'user'. ts(2304)
,因为它在 ...auth
扩展运算符中返回时看不到对象 user
。
我不确定它为什么会这样,或者如何解决它。
这是 Auth0 插件的代码。简而言之,它使用app.provide("Auth", authPlugin);
提供对一堆东西的访问,包括user
对象:
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) =>
app.provide("Auth", authPlugin);
,
;
interface Auth0Plugin
init(options: Auth0PluginOptions): Promise<Plugin>;
routeGuard: NavigationGuardWithThis<undefined>;
export const Auth0: Auth0Plugin =
init,
routeGuard,
;
在我的Profile.vue
页面中,我使用const auth = inject<Auth0Client>("Auth")!;
注入Auth0 插件,并使用...auth
扩展运算符从setup()
返回其所有内容。这包括现在可以在模板中使用的 user
对象。
所有这些都在前端工作。它按预期显示字符串化的user
对象。
但是 vscode 抛出 Cannot find name 'user'. ts(2304)
错误,因为 user
对象没有从 setup()
显式返回。
似乎它不知道...auth
扩展运算符在auth
内部有user
对象:
<template>
<div class="about">
<h1>This is a profile page, only logged in users can see it.</h1>
</div>
<div class="row">
JSON.stringify(user, null, 2) <!-- ERROR: Cannot find name 'user'.ts(2304) -->
</div>
</template>
<script lang="ts">
import Auth0Client from "@auth0/auth0-spa-js";
import inject from "vue";
export default
name: "Profile",
setup()
const auth = inject<Auth0Client>("Auth")!;
return
...auth,
;
,
;
</script>
我试图通过显式返回user
对象来解决这个问题,如下所示,但它破坏了功能。字符串化的user
对象不再显示在前端:
<template>
<div class="about">
<h1>This is a profile page, only logged in users can see it.</h1>
</div>
<div class="row">
JSON.stringify(auth_user, null, 2)
</div>
</template>
<script lang="ts">
import Auth0Client from "@auth0/auth0-spa-js";
import inject from "vue";
export default
name: "Profile",
setup()
const auth = inject<Auth0Client>("Auth")!;
const auth_user = auth.getUser(); // This does not work
//const auth_user = auth.user; // This variation also doesn't work
return
auth_user,
;
,
;
</script>
谁能弄清楚这里发生了什么以及如何解决错误?
【问题讨论】:
【参考方案1】:有几个问题:
Auth0Client
class 没有user
字段,因此从setup()
返回 ...auth
不会创建user
属性。但这不是您想要的类型,我们将在下一点看到。
export default class Auth0Client
private options;
private transactionManager;
private cacheManager;
private customOptions;
private domainUrl;
private tokenIssuer;
private defaultScope;
private scope;
private cookieStorage;
private sessionCheckExpiryDays;
private orgHintCookieName;
private isAuthenticatedCookieName;
private nowProvider;
cacheLocation: CacheLocation;
private worker;
constructor(options: Auth0ClientOptions);
private _url;
private _getParams;
private _authorizeUrl;
private _verifyIdToken;
private _parseNumber;
private _processOrgIdHint;
buildAuthorizeUrl(options?: RedirectLoginOptions): Promise<string>;
loginWithPopup(options?: PopupLoginOptions, config?: PopupConfigOptions): Promise<void>;
getUser<TUser extends User>(options?: GetUserOptions): Promise<TUser | undefined>;
getIdTokenClaims(options?: GetIdTokenClaimsOptions): Promise<IdToken>;
loginWithRedirect(options?: RedirectLoginOptions): Promise<void>;
handleRedirectCallback(url?: string): Promise<RedirectLoginResult>;
checkSession(options?: GetTokenSilentlyOptions): Promise<void>;
getTokenSilently(options: GetTokenSilentlyOptions &
detailedResponse: true;
): Promise<GetTokenSilentlyVerboseResponse>;
getTokenSilently(options?: GetTokenSilentlyOptions): Promise<string>;
private _getTokenSilently;
getTokenWithPopup(options?: GetTokenWithPopupOptions, config?: PopupConfigOptions): Promise<string>;
isAuthenticated(): Promise<boolean>;
buildLogoutUrl(options?: LogoutUrlOptions): string;
logout(options?: LogoutOptions): Promise<void> | void;
private _getTokenFromIFrame;
private _getTokenUsingRefreshToken;
private _getEntryFromCache;
-
虽然
Auth
对象是 inject
ed 作为 Auth0Client
,但 actual object provide
d in @/auth/index.ts
具有不与 Auth0Client
重叠的类型。应该导出实际类型,以便inject
Auth
对象的组件可以键入引用:
const authPlugin =
isAuthenticated: computed(() => state.isAuthenticated),
loading: computed(() => state.loading),
user: computed(() => state.user),
getIdTokenClaims,
getTokenSilently,
getTokenWithPopup,
handleRedirectCallback,
loginWithRedirect,
logout,
;
export type ProvidedAuthPlugin = typeof authPlugin; ?
⋮
app.provide("Auth", authPlugin);
-
对于enable TypeScript support in a component(包括
<template>
内),组件定义应声明为defineComponent
:
import defineComponent from "vue";
export default defineComponent(
⋮
);
-
并且
Auth
对象的类型应该在inject
ing它时在组件中使用:
import type ProvidedAuthPlugin from "@/auth"; ?
import inject, defineComponent from "vue";
export default defineComponent(
name: "Profile",
setup() ?
const auth = inject("Auth") as ProvidedAuthPlugin;
return
...auth,
;
,
);
GitHub PR
【讨论】:
【参考方案2】:就我的理解而言,还可以(我不是组合 API 方面的专家)。
例如在setup()
中,return
语句应该为您提供<template>
中可用的内容。
假设你想在这里使用用户
<div class="row">
JSON.stringify(user, null, 2) <!-- ERROR: Cannot find name 'user'.ts(2304) -->
</div>
基本上它找不到任何类型的user
数据。让我们尝试将其添加到setup()
的return
语句中
试试这个:
<template>
<div class="about">
<h1>This is a profile page, only logged in users can see it.</h1>
</div>
<div class="row">
JSON.stringify(user, null, 2)
</div>
</template>
<script lang="ts">
import inject, ref from 'vue'
import Auth0Client, User from '@auth0/auth0-spa-js'
export default
name: 'Profile',
setup()
/* Added for you this 2 lines, one for getting types of auth
I think the other one is reactive */
const auth = inject('Auth') as Auth0Client
const user = ref<User | undefined>(undefined)
auth.getUser().then((authuser) => (user.value = authuser))
return
...auth, // Check this one, I don't see it being used in <template>
user // This one should be available in <template> now
</script>
希望它可以工作...另外,如果您出于某种原因刚刚学习 Vue 使用默认 API,那么我不是组合 API 的忠实粉丝,它更容易学习和使用 :)。
【讨论】:
感谢您的帮助,但这破坏了个人资料页面上user
的显示。我认为它以某种方式使其无反应。
我认为在原始...auth
上的传播运算符有助于返回user
,它位于auth
内部。我仍在学习传播运算符,所以我可能是错的,但我认为这就是它以前起作用的原因。
副作用转到 onMounted,将它们直接放入设置是一种不好的做法。或者他们可以在设置中等待,但这会迫使它有悬念地使用。
@EstusFlask 您能否详细说明答案?我不完全明白
你需要把auth.getUser()
放在onMounted里面,或者如果你想让组件在没有数据的情况下不渲染,就等待v3.vuejs.org/guide/migration/suspense.html#introduction。以上是关于Vue 3 + TypeScript:使用 JS 扩展运算符从 setup() 返回的对象在 vscode 中引发错误的主要内容,如果未能解决你的问题,请参考以下文章
从 TypeScript 功能的角度来看,Vue.js 3 是不是可以实现基于类的语法?
Webpack 4,Vue 同时使用 Typescript 类和 JS 代码。
Vue js,使用 TypeScript 从 Vue 中的表单获取数据
typescript 使用Typescript将属性添加到Vue.js中的数组