带有打字输入的VUE 3不能按预期工作。只能从对象类型创建跨页类型

2022-04-07 vue.js typescript vuejs3
Vue 3和TypeScrip会出现错误(见下文),但仅在出现脚本lang=&t&q;时才会出现。谁能解释一下为什么Vue 3中的inject和TypeScrip标签不起作用?

ERROR in src/components/global/HeaderMenu.vue:85:7
TS2698: Spread types may only be created from object types.
    83 |     const auth = inject('Auth');
    84 |     return {
  > 85 |       ...auth,
       |       ^^^^^^^
    86 |     };
    87 |   },
    88 | });

工作示例:

<script>
import { defineComponent } from 'vue';
import { inject } from 'vue';
export default defineComponent({
  name: 'HeaderMenu',
  inject: ['Auth'],
  methods: {
    login() {
      this.Auth.loginWithRedirect();
    },
    logout() {
      this.Auth.logout();
      this.$router.push({ path: '/' });
    },
  },
  setup() {
    const auth = inject('Auth');
    return {
      ...auth,
    };
  },
});
</script>

生成上述错误的示例:

<script lang="ts">
import { defineComponent } from 'vue';
import { inject } from 'vue';
export default defineComponent({
  name: 'HeaderMenu',
  inject: ['Auth'],
  methods: {
    login() {
      this.Auth.loginWithRedirect();
    },
    logout() {
      this.Auth.logout();
      this.$router.push({ path: '/' });
    },
  },
  setup() {
    const auth = inject('Auth');
    return {
      ...auth,
    };
  },
});
</script>

Main.js

import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
// Auth Service
import { Auth0 } from '@/auth';

import BootstrapVue3 from 'bootstrap-vue-3';
// Import Bootstrap an BootstrapVue CSS files (order is important)
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap-vue-3/dist/bootstrap-vue-3.css';


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: process.env.VUE_APP_AUTH0_AUDIENCE,
    redirectUri: window.location.origin,
  });
  const app = createApp(App);
  app
    .use(AuthPlugin)
    .use(router)
    .use(BootstrapVue3)
    .mount('#app');
}

init();

Auth.js示例

 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
}

解决方案

编译器无法从inject(...)调用确定auth类型,因为它无法将其与各自调用相关联。需要为其提供类型信息。

authPlugin类型需要曝光,如:

export type TAuthPlugin = typeof authPlugin;

则需要提供给inject。根据经验,像inject这样的函数是通用的:

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

相关文章