我在try 使用side组件(在项目目录之外)时遇到以下类型脚本错误:

TS2345: Argument of type '{ template: string; components: { SimpleCheckbox: typeof SimpleCheckbox; }; }' 
is not assignable to parameter of type 'VueClass<Vue>'.
Object literal may only specify known properties, and 'template' does not exist in type 
'VueClass<Vue>'.

我的WebStorm IDE没有检测到这个错误;当我用TypeScript loader运行Webpack时,在控制台中输出了.

错误发生在:

import { Vue, Component, Prop } from 'vue-property-decorator';
import template from './SkipProjectInitializationStepPanel.pug';
import SimpleCheckbox from './../../../../ui-kit-libary-in-development/UiComponents/Checkboxes/MaterialDesign/SimpleCheckbox.vue';


@Component({ template, components: { SimpleCheckbox } }) // here !
export default class SkipProjectInitializationStepPanel extends Vue {
  @Prop({ type: String, required: true }) private readonly text!: string;
}

ui-kit-libary-in-development name中可以看出,这还不是npm依赖项,所以目前还不在node_modules中.

这完全是打字错误;尽管ts-loader抛出了这个错误,但Webpack构建了我的项目,并且编译的JavaScript工作正常.如果执行以下操作之一,此错误将消失:

  • SimpleCheckbox.vue移动到与SkipProjectInitializationStepPanel.ts相同的目录,并将其作为import SimpleCheckbox from './SimpleCheckbox.vue';导入
  • @Component({ template, components: { SimpleCheckbox } })中移除SimpleCheckbox,只留下@Component({ template, components: {} })(当然,SimpleCheckbox在本例中不会被渲染,但它证明问题不在SkipProjectInitializationStepPanel中).
  • 将主项目的ui-kit-libary-in-development移到node_modules,并从ui-kit-libary-in-development中移除node_modules(如果不移除,则不会有任何更改).

不幸的是,我无法重现这个问题.出于以下原因,try 复制作品时不会出现错误:

MainProject/src/Application.vue

<template lang="pug">
  PageOne
</template>

<script lang="ts">
  import { Vue, Component } from 'vue-property-decorator';
  import PageOne from './PageComponents/PageOne'

  @Component({ components: { PageOne }})
  export default class Application extends Vue {
    private created(): void {
      console.log('Done.');
    }
  }
</script>

MainProject/src/PageComponents/PageOne.ts

import { Vue, Component, Prop } from 'vue-property-decorator';
import template from './PageOne.pug';
import Button from './../../../UiKitLibraryStillInDevelopment/UiComponents/Buttons/Button.vue';


@Component({ template, components: { Button } })
export default class SkipProjectInitializationStepPanel extends Vue {}

MainProject/src/PageComponents/PageOne.pug

.RootElement
  Button(:text="'Click me'")

ui-kit-libary-in-development/UiComponents/Buttons/Button.vue

<template lang="pug">
  button {{ text }}
</template>


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

  @Component
  export default class SimpleCheckbox extends Vue {
    @Prop({ type: String, required: true }) private readonly text!: string;

    private created(): void {
      console.log('OK!');
      console.log(this.$props);
    }
  }
</script>

我发现的所有线索都是第Setting components in Component decorator causes Typescript 2.4 error期的 comments :

侧部组件应添加.d、 it's for it work AFAIK.

Nick Messing

根据这条线索,出现了以下问题:

  • 我应该在哪里创建.d.ts-在我的主要项目或依赖项中?最有可能是在主项目中,但如果是这样,为什么我可以在第三方库(如vuetify)中导入侧组件?因为那里有.d.ts个!
  • 我需要如何在.d.ts中声明新的Vue组件?一些教程或例子?

赏金的源文件

因为我无法重现这个问题,而且我的项目仍然是原始的(还没有商业价值),所以我可以通过Google Drive(link for downloading zip archive)分享它.所有node_modules都包括在内,只需在main-project目录中运行npm run developmentBuild即可.

如果你担心潜在的病毒,你也可以在this repository中获得源文件,但因为它不包括node_modules,为了复制,需要在main-projectdependency目录中执行npm install.

推荐答案

这已经成为一个很长的答案.如果你没有时间全部阅读,那就有TL;DR at the end分了.


Analysis

错误消息

起初,我真的不明白为什么TypeScript提到VueClass<Vue>并抱怨template的房产.然而,当我查看Component decorator的类型定义时,事情变得更清楚了:

vue-class-component/lib/index.d.ts(省略部分)

declare function Component<V extends Vue>(options: ComponentOptions<V> & ThisType<V>): <VC extends VueClass<V>>(target: VC) => VC;
// ...
declare function Component<VC extends VueClass<Vue>>(target: VC): VC;

我们可以看到Component有两个签名.第一个和你一样使用,第二个没有选项(@Component class Foo).

显然,编译器认为我们的用法与第一个签名不匹配,所以它必须是第二个签名.因此,我们最终会得到一条错误消息VueClass<Vue>.

Note:在最新版本(3.6.3)中,TypeScript实际上会显示一个更好的错误,说明两个重载以及它们不匹配的原因.

Better 错误消息

接下来我做的事情是暂时注释掉main-project/node_modules/vue-class-component中的第二个函数声明,果然我们得到了一条不同的错误消息.这条新消息跨越63行,所以我觉得把它作为一个整体包含在这篇文章中是没有意义的.

ERROR in /tmp/main-project/InitializeProjectGUI__assets/SingletonComponents/SkipProjectInitializationStepPanel/SkipProjectInitializationStepPanel.ts
../InitializeProjectGUI__assets/SingletonComponents/SkipProjectInitializationStepPanel/SkipProjectInitializationStepPanel.ts
[tsl] ERROR in /tmp/main-project/InitializeProjectGUI__assets/SingletonComponents/SkipProjectInitializationStepPanel/SkipProjectInitializationStepPanel.ts(10,24)
      TS2322: Type '{ SimpleCheckbox: typeof SimpleCheckbox; }' is not assignable to type '{ [key: string]: VueConstructor<Vue> | FunctionalComponentOptions<any, PropsDefinition<any>> | ComponentOptions<never, any, any, any, any, Record<string, any>> | AsyncComponentPromise<any, any, any, any> | AsyncComponentFactory<...>; }'.
  Property 'SimpleCheckbox' is incompatible with index signature.
    Type 'typeof SimpleCheckbox' is not assignable to type 'VueConstructor<Vue> | FunctionalComponentOptions<any, PropsDefinition<any>> | ComponentOptions<never, any, any, any, any, Record<string, any>> | AsyncComponentPromise<any, any, any, any> | AsyncComponentFactory<...>'.
      Type 'typeof SimpleCheckbox' is not assignable to type 'VueConstructor<Vue>'.
        Types of property 'extend' are incompatible.
          Type '{ <Data, Methods, Computed, PropNames extends string = never>(options?: import("/tmp/dependency/node_modules/vue/types/options").ThisTypedComponentOptionsWithArrayProps<import("/tmp/depende...' is not assignable to type '{ <Data, Methods, Computed, PropNames extends string = never>(options?: import("/tmp/main-project/node_modules/vue/types/options").ThisTypedComponentOptionsWithArrayProps<import("/tmp/main-...'.
            ...
              Type 'import("/tmp/dependency/node_modules/vue/types/vnode").ScopedSlotReturnValue' is not assignable to type 'import("/tmp/main-project/node_modules/vue/types/vnode").ScopedSlotReturnValue'.
                Type 'VNode' is not assignable to type 'ScopedSlotReturnValue'.

正如您所看到的,错误消息很难阅读,并且并不真正指向特定的问题.因此,我开始着手降低项目的复杂性,以便更好地理解这个问题.

最低限度的例子

我会让你省go 整个过程,其中涉及大量的try 和错误.让我们直接跳到结果.

struct

├─ main-project
│  ├─ node_modules // installed packages listed below (without sub-dependencies)
│  │     ts-loader@6.1.0
│  │     typescript@3.6.3
│  │     vue@2.6.10
│  │     vue-property-decorator@8.2.2
│  │     vuex@3.1.1
│  │     webpack@4.40.2
│  │     webpack-cli@3.3.9
│  ├─ SkipProjectInitializationStepPanel.ts
│  ├─ tsconfig.json
│  └─ webpack.config.js
└─ dependency
   ├─ node_modules // installed packages listed below (without sub-dependencies)
   │     vue@2.6.10
   └─ SimpleCheckbox.ts

主要项目/tsconfig.json

{
  "compilerOptions": {
    "target": "es6",
    "strict": true,
    "moduleResolution": "node"
  }
}

main-project/webpack.config.js:

module.exports = {
    entry: './SkipProjectInitializationStepPanel.ts',
    mode: 'development',
    module: {
        rules: [
            {
                test: /\.ts$/,
                loader: 'ts-loader'
            }
        ]
    },
    resolve: {
        extensions: ['.ts', '.js']
    }
};

main-project/SkipProjectInitializationStepPanel.ts:

import { Component } from 'vue-property-decorator';
import 'vuex';

import SimpleCheckbox from '../dependency/SimpleCheckbox';

Component({ template: '', components: { SimpleCheckbox } });

dependency/SimpleCheckbox.ts:

import Vue from 'vue';

export default class SimpleCheckbox extends Vue {}

当从main-projectnpm run webpack运行这个例子时,我们得到了与之前完全相同的错误.任务完成了.

发生了什么事?

当我把项目的一部分删go ,把它简化为这个最小的例子时,我学到了一些非常有趣的东西.

你可能已经注意到我在SkipProjectInitializationStepPanel.ts上加了import 'vuex'.在原始项目中,vuex当然是从不同的地方(例如main-project/Source/ProjectInitializer/Store/Store.ts)导入的,但这对于复制问题并不重要.关键是这一点.

要了解为什么导入vuex会导致这个问题,我们必须查看vuex的类型定义.

vuex/types/index.d.ts(前几行)

import _Vue, { WatchOptions } from "vue";

// augment typings of Vue.js
import "./vue";

import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from "./helpers";

这里我们对第二个import感兴趣.这个 comments 实际上已经给了我们一个提示:augment typings of Vue.js.

vuex/types/vue.d.ts(省略部分)

import { Store } from "./index";

// ...

declare module "vue/types/vue" {
  interface Vue {
    $store: Store<any>;
  }
}

罪魁祸首来了.用vue来增加vuex的接口类型.似乎这种扩展只发生在node_modulesvuex相同的"本地"类型上.因为main-project是增加的Vuedependency是原来的Vue,所以两者不匹配.

TS装载机

在我所有的测试过程中,我无法用tsc次就重现这个问题.显然ts-loader做了一些不同的事情导致了这个问题.这可能与使用Webpack进行模块解析有关,也可能是完全不同的事情.我不知道.

Solution Approaches

我对如何解决这个问题有一些 idea .虽然这些不一定是现成的解决方案,而是不同的方法和 idea .

Add vuex to dependency

由于从main-project中删除vuex并不是一个真正的选项,我们唯一能做的就是让这两个Vue接口匹配,也就是在dependency中包含vuex.

奇怪的是,我能够在我的最小示例中实现这个修复,但在最初的项目中无法实现.我还不明白为什么会这样.

除此之外,它不是很优雅,您可能需要从main-project引用的每个文件中导入vuex个.

Use a shared node_modules

拥有一个共享的node_modules文件夹意味着两个项目使用相同的vue,所以这个问题就消失了.根据您的需求,以共享同一文件夹的方式组织这两个项目可能是一个很好的解决方案.您可能还想看看像LernaYarn workspaces这样的工具,它们可以帮助您实现这一点.

Consume dependency as a package

你说dependency is not [an] npm-dependency yet.也许是时候做一个了.与共享node_modules目录类似,这将导致两个项目使用相同的vue安装,从而解决问题.

进一步调查

如前所述,这种情况只发生在ts-loader人身上.Maybe有一些东西可以在ts-loader中修复或配置,以避免这个问题.

TL;DR

main-project导入vuexdependency不导入.vuex使用declaration mergingVue接口添加$store属性来扩充Vue接口.现在,一个项目的Vue与另一个项目的Vue不匹配,从而导致错误.

Vue.js相关问答推荐

VueJS不会呈现一个名为None的组件""

如何在与父集合相同的读取中取回子集合(或重构数据?)

可组合数据获取示例

在 Nuxt3 中使用 Vue3 vue-ganntastic 插件

在 VueJS 中,将变量而不是组件的字符串名称传递给动态组件的 :is 属性不起作用

使用 Nuxt 动态获取文件夹中的图像路径

Vue index.html favicon 问题

使用 v-slot:body 时 Vuetify v-data-table 没有响应

Vue 单元测试:您正在开发模式下运行 Vue?

将检测外部点击自定义指令从 Vue 2 迁移到 Vue 3

Bootstrap-vue b-table,标题中带有过滤器

Polyfill for ie

PhpStorm 中Expected预期表达式

我如何能够在 vue js html 中以给定格式进行多项 Select 和传递数据?

$set 不是函数

使用 Vue.js 获取所有选中复选框的列表

VueJS 复选框更新

检测 Vue-Router 导航Guards中的后退按钮

Vuetify 离线文档

使用 svelte js 的原因