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&lt;Auth0Client&gt;("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 对象是 injected 作为 Auth0Client,但 actual object provided 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(包括&lt;template&gt;内),组件定义应声明为defineComponent
import  defineComponent  from "vue";

export default defineComponent(
  ⋮
);
    并且Auth对象的类型应该在injecting它时在组件中使用:
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 语句应该为您提供&lt;template&gt; 中可用的内容。

假设你想在这里使用用户

 <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中的数组

使用 Typescript 的反应式 Vue Chart.js 组件

如何获得 Vue.js 2.0 类型的 TypeScript 与 Visual Studio 一起使用?