Vue&TypeScript:在项目目录之外的TypeScript组件中实施导入时,如何避免错误TS2345?

Gur*_*ofu 5 typescript vue.js vue-component typescript-decorator typescript-definitions

尝试使用辅助组件(项目目录外部)时出现TypeScript错误:

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>'.
Run Code Online (Sandbox Code Playgroud)

我的WebStorm IDE没有检测到此错误;当我使用TypeScript加载程序运行Webpack时,in在控制台中输出。

错误发生在:

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;
}
Run Code Online (Sandbox Code Playgroud)

ui-kit-libary-in-development名称上可以看出,这还不是npm依赖关系,因此暂时不node_modules存在。

这完全是TypeScript错误。尽管ts-loader会引发此错误,但Webpack会构建我的项目,并且已编译的JavaScript可以正常工作。如果执行以下操作之一,此错误将消失:

  • 移动SimpleCheckbox.vue到相同的目录SkipProjectInitializationStepPanel.ts并导入为import SimpleCheckbox from './SimpleCheckbox.vue';
  • SimpleCheckbox从中删除@Component({ template, components: { SimpleCheckbox } })并仅离开@Component({ template, components: {} })(当然,SimpleCheckbox在这种情况下将不会呈现,但是证明问题不在SkipProjectInitializationStepPanel)。
  • 移动ui-kit-libary-in-developmentnode_modules主项目和删除node_modulesui-kit-libary-in-development(如果不删除,一切都不会改变)。

不幸的是,我无法重现此问题。由于某些原因,下面的重试效果没有错误:

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>
Run Code Online (Sandbox Code Playgroud)

MainProject / src / PageComponents / PageOne.ts

<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>
Run Code Online (Sandbox Code Playgroud)

MainProject / src / PageComponents / PageOne.pug

.RootElement
  Button(:text="'Click me'")
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

我发现的所有线索都是该问题的注释,在组件装饰器中设置组件会导致Typescript 2.4错误

侧面组件应添加.d.ts才能正常运行。

尼克·梅辛

从这个线索中,产生了以下问题:

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

赏金源文件

因为我无法重现此问题,并且我的项目仍然是原始的(尚未获得商业价值),所以我可以通过Google云端硬盘(下载zip存档的链接)进行共享。全部node_modules包含在内,仅npm run developmentBuildmain-project目录中运行。

如果您担心潜在的病毒,也可以在此存储库中获取源文件,但由于它不包含node_modules,因此,要进行复制,必须npm installmain-projectdependency目录中都执行该文件。

luk*_*ter 5

这已经是一个很长的答案。如果您没有时间阅读所有内容,请在末尾找到TL; DR


分析

错误信息

最初,我不太了解TypeScript为什么提到VueClass<Vue>和抱怨该template属性。但是,当我查看Component装饰器的类型定义时,情况变得更加清晰:

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;
Run Code Online (Sandbox Code Playgroud)

我们在这里可以看到Component有两个签名。第一个像您一样使用,第二个不带选项(@Component class Foo)。

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

注意:在最新版本(3.6.3)中,TypeScript实际上会显示一个更好的错误,说明重载和不匹配的原因。

更好的错误信息

我要做的下一件事是暂时注释掉第二个函数声明,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'.
Run Code Online (Sandbox Code Playgroud)

如您所见,该错误消息非常难以阅读,并且并未真正指出特定的问题。因此,我改为着手开始降低项目的复杂性,以便更好地理解问题。

最小的例子

我将为您省去涉及很多试验和错误的整个过程。让我们直接跳转到结果。

结构体

?? 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
Run Code Online (Sandbox Code Playgroud)

main-project/tsconfig.json

{
  "compilerOptions": {
    "target": "es6",
    "strict": true,
    "moduleResolution": "node"
  }
}
Run Code Online (Sandbox Code Playgroud)

main-project/webpack.config.js

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'.
Run Code Online (Sandbox Code Playgroud)

main-project/SkipProjectInitializationStepPanel.ts

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

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

Component({ template: '', components: { SimpleCheckbox } });
Run Code Online (Sandbox Code Playgroud)

dependency/SimpleCheckbox.ts

import Vue from 'vue';

export default class SimpleCheckbox extends Vue {}
Run Code Online (Sandbox Code Playgroud)

当使用main-projectwith 运行此示例时npm run webpack,我们将获得与之前完全相同的错误。任务完成。

发生了什么?

当我删除项目的各个部分以简化为这个最小的示例时,我学到了一些非常有趣的东西。

您可能已经注意到import 'vuex'我已添加到中SkipProjectInitializationStepPanel.tsvuex当然,原始项目是从不同的地方(例如main-project/Source/ProjectInitializer/Store/Store.ts)导入的,但这对于重现该问题并不重要。至关重要的是,main-project进口vuexdependency不进口

为了找出导致导入的vuex原因,我们必须查看的类型定义vuex

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

?? 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
Run Code Online (Sandbox Code Playgroud)

在这里我们对第二个感兴趣import。该评论实际上已经给了我们提示:扩充Vue.js的类型

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

{
  "compilerOptions": {
    "target": "es6",
    "strict": true,
    "moduleResolution": "node"
  }
}
Run Code Online (Sandbox Code Playgroud)

这是罪魁祸首。这些vuex类型利用声明合并添加$store到的Vue接口vue。看来,这增强只发生在同一个“本地”类型node_modulesvuex。因为main-project具有增强的Vuedependency原始的,所以两个不匹配。

TS装载机

在我所有的测试过程中,我无法用just来重现问题tsc。显然ts-loader,造成此问题的方式有所不同。它可能与使用Webpack进行模块解析有关,也可能完全不同。我不知道。

解决方法

我有一些想法可以解决或解决此问题。尽管这些不一定是立即可用的解决方案,而是不同的方法和想法。

添加vuexdependency

作为除去vuexmain-project是不是一个真正的选择,我们可以做的唯一的事情,使这两个Vue接口匹配,则包括vuexdependency也是如此。

奇怪的是,我能够在最小的示例中获得此修复程序,但在原始项目中却没有。我还不知道为什么。

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

使用共享 node_modules

拥有共享node_modules文件夹意味着两个项目使用相同的文件夹,vue因此该问题就消失了。根据您的要求,以共享相同node_modules文件夹的方式组织两个项目可能是一个很好的解决方案。您可能还想看一下LernaYarn工作区之类的工具,它们可以为您提供帮助。

dependency打包消费

您说那dependency 还不是[npm]依赖性。也许是时候做到这一点了。与共享node_modules目录类似,这将导致两个项目使用相同的vue安装,这将解决此问题。

进一步调查

如前所述,这仅在发生ts-loader也许可以进行一些固定或配置ts-loader来避免此问题。

TL; DR

main-project导入vuexdependency没有。使用声明合并vuex增加Vue接口,向其添加属性。现在,来自一个项目的与另一个项目不匹配,从而导致错误。$storeVueVue

  • 你的回答假装是论文。非常深入的研究;我目前的知识还不够。仅仅说一声“谢谢”是不够的,但不幸的是我的赏金最终没有获胜者。我相信卡瓦诺先生给了你声誉奖。 (2认同)