在 TypeScript Quasar 引导文件中扩展 Vue 实例接口

Extending Vue instance interface in TypeScript Quasar boot file

编辑:我在下面的 post 中多次说过我“不在模块中”。任何时候一个文件中的 JavaScript 代码被另一个从技术上讲是模块的文件调用。我想说的是,这个模块在脚本初始化时由 Quasar 自动调用,预计会产生全局副作用。该模块从未导入到我编写的任何代码中。

简洁 post:

我正在使用支持 TypeScript 和 Vue Class 组件的 Quasar 2.0.0。我希望添加一个实例 属性 以便在我的 classes 中访问:this.$fetch(...);

我创建了一个引导文件,内容如下:

import Vue from "vue";

declare module "vue/types/vue"
{
    interface Vue
    {
        prototype: {
            $fetch: (resource: (string | Request), init?: RequestInit) => Promise<Response>
        },
        $fetch: (resource: (string | Request), init?: RequestInit) => Promise<Response>
    }
}

type Context = {
    Vue: Vue
};

export default async ({ Vue: vue }:Context) =>
{
    vue.prototype.$fetch = async (resource, init) =>
    {
        return fetch(resource, init);
    };
};

这不会引发 TypeScript 的任何错误。但是当我尝试在组件中使用它时,出现错误:

<template>
    <q-layout>
        <q-page-container>
            <q-page class="flex flex-center">
                <h1>Test</h1>
            </q-page>
        </q-page-container>
    </q-layout>
</template>

<script lang="ts">
import { Vue, Component } from "vue-property-decorator";

@Component
export default class TestPage extends Vue
{
    mounted ()
    {
        this.$fetch("https://www.google.com");
    }
}
</script>

Property '$fetch' does not exist on type 'TestPage'.

上一个(杂乱无章)post:

我正在开发 Quasar 应用程序,为了方便起见,我想创建一个修改后的 fetch 方法,如果请求的域与我的后端域匹配,它会注入我的 API 密钥.界面类似于:

@Component
export default class VueComponent extends Vue
{
    mounted()
    {
        this.$fetch("https://www.example.com/api/endpoint").then(console.log);
    }
}

我知道如何定义这样的东西。我只需要根据 https://vuejs.org/v2/cookbook/adding-instance-properties.html:

更新 Vue 原型
Vue.prototype.$fetch = async function(request, init)
{
    // ... blah ...
};

根据 Quasar's documentation,更新 V​​ue 原型的正确时间是在 引导文件 中。所以我创建了一个:

quasar new boot fetch

我的引导文件开始时相当简单:

export default async ({ Vue }) =>
{
    Vue.prototype.$fetch = async (resource, init) =>
    {
        return fetch(resource, init);
    };
};

当我尝试转换为 TypeScript 时,一切都崩溃了。参数 Vue 隐式具有 any 类型,因为没有为参数对象提供类型。

我试过这样定义类型:

import Vue from "vue";

type Context = {
    Vue: Vue
};

export default async ({ Vue }:Context) =>

然而,这引起了对变量阴影的抱怨(Vue 在上层范围中定义)。我注意到 Vue 变量的类型是 VueConstructor<Vue>,所以我改为尝试:

import { VueConstructor } from "vue";

type Context = {
    Vue: VueConstructor
};

export default async ({ Vue }:Context) =>

这适用于函数定义,但在 Vue.prototype.$fetch = ... 行上引发错误:“不安全的成员访问 .$fetch on any value”。 TypeScript 不希望 Vue.prototype$fetch 属性。如果我正在做一个模块定义,我会像这样扩展接口:

declare module "abc" {
    VueConstructor: {
        prototype: {
            $fetch: ...
        }
    }
}

但是我没有声明模块——这是一个引导文件。我尝试了一个快速的 hack 来解决这个问题,只在我的引导文件的上下文中定义我自己的 VueConstructor

interface VueConstructor
{
    prototype: {
        $fetch: (resource: (string | Request), init?: RequestInit) => Promise<Response>
    }
}

type Context = {
    Vue: VueConstructor
};

export default async ({ Vue }:Context) =>
{
    Vue.prototype.$fetch = async (resource: (string | Request), init?: RequestInit) =>
    {
        return fetch(resource, init);
    };
};

这终于让 TypeScript 停止抱怨 this 文件,但是当我尝试在一个文件中使用 this.$fetch组件:

Property '$fetch' does not exist on type 'VueComponent'.

由于 VueComponent 扩展了 Vue 我想我需要更新 Vue class 的定义,但我不知道如何做。我尝试将以下内容添加到我的引导文件中:

interface Vue
{
    $fetch: (resource: (string | Request), init?: RequestInit) => Promise<Response>
}

但这没有用。我不能只使用 declare module 因为我不在模块中。如何在没有模块的情况下全局扩展类型?

我的“简洁 post”中的代码示例(展示了我在反复试验后得出的结论)确实有效——我只需要重新启动我的计算机(可能只需要重新启动我的 IDE 但由于某种原因点击关闭并没有完全关闭它)