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

2024-01-08

当尝试使用侧面组件(在项目目录之外)时,我遇到了以下 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>'.

我的 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;
}

如下来自ui-kit-libary-in-developmentname,这还不是 npm 依赖项,所以它不在里面node_modules目前。

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

  • Move SimpleCheckbox.vue到同一目录SkipProjectInitializationStepPanel.ts并将其导入为import SimpleCheckbox from './SimpleCheckbox.vue';
  • Remove SimpleCheckbox from @Component({ template, components: { SimpleCheckbox } })并只留下@Component({ template, components: {} })(当然,SimpleCheckbox在这种情况下不会被渲染,但它证明问题不在于SkipProjectInitializationStepPanel).
  • Move ui-kit-libary-in-development to node_modules主要项目并删除node_modules from ui-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>

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-library-in-development/Ui 组件/按钮/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>

我发现的所有线索都是这个评论在组件装饰器中设置组件会导致 Typescript 2.4 错误 https://github.com/vuejs/vue-class-component/issues/123:

侧面组件应该添加 .d.ts 才能正常工作。

尼克·梅辛 https://github.com/nickmessing

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

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

赏金的源文件

因为我无法重现这个问题,而且我的项目仍然是原始的(还没有商业价值),我可以通过Google Drive分享它(下载 zip 存档的链接 https://drive.google.com/file/d/1MWCBc1jppZcvRfh0622f69CtU8Sf5WFO/view). All node_modules包含在内,只需运行npm run developmentBuild in main-project目录。

如果您担心潜在的病毒,您还可以在以下位置获取源文件这个存储库 https://github.com/TokugawaTakesi/questions-can_not_import_type_script_component_from_outer_project,但因为它不包括node_modules,为了重现它需要执行npm install同时main-project and dependency目录。


这已经成为一个相当长的答案。如果您没有时间阅读全部内容,可以阅读TL;DR在最后.


Analysis

错误信息

一开始我不太明白为什么 TypeScript 会提到VueClass<Vue>并抱怨template财产。然而,当我查看类型定义时Component装饰器,事情变得更加清晰了:

vue-class-component/lib/index.d.ts https://unpkg.com/browse/vue-class-component@7.1.0/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 实际上会显示更好的错误,说明两个重载以及它们不匹配的原因。

更好的错误消息

我做的下一件事是暂时注释掉第二个函数声明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'.

正如您所看到的,错误消息很难阅读,并且并没有真正指出特定的问题。因此,我继续降低项目的复杂性,以便更好地理解问题。

最小的例子

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

结构

├─ main-project
│  ├─ node_modules // installed packages listed below (without sub-dependencies)
│  │     [email protected] /cdn-cgi/l/email-protection
│  │     [email protected] /cdn-cgi/l/email-protection
│  │     [email protected] /cdn-cgi/l/email-protection
│  │     [email protected] /cdn-cgi/l/email-protection
│  │     [email protected] /cdn-cgi/l/email-protection
│  │     [email protected] /cdn-cgi/l/email-protection
│  │     [email protected] /cdn-cgi/l/email-protection
│  ├─ SkipProjectInitializationStepPanel.ts
│  ├─ tsconfig.json
│  └─ webpack.config.js
└─ dependency
   ├─ node_modules // installed packages listed below (without sub-dependencies)
   │     [email protected] /cdn-cgi/l/email-protection
   └─ SimpleCheckbox.ts

main-project/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-project with npm run webpack,我们得到与之前完全相同的错误。任务完成。

发生了什么?

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

您可能已经注意到import 'vuex'我已经添加到SkipProjectInitializationStepPanel.ts。在原来的项目中vuex当然是从不同的地方进口的(例如main-project/Source/ProjectInitializer/Store/Store.ts)但这对于重现问题并不重要。关键的部分是main-project进口vuex and dependency doesn't.

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

vuex/types/index.d.ts https://unpkg.com/browse/vuex@3.1.1/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。该评论实际上已经给了我们提示:增强 Vue.js 的类型.

vuex/types/vue.d.ts https://unpkg.com/browse/vuex@3.1.1/types/vue.d.ts(省略部分)

import { Store } from "./index";

// ...

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

罪魁祸首就在这里。这vuex类型利用声明合并 https://www.typescriptlang.org/docs/handbook/declaration-merging.html to add $store to the Vue的接口vue。看来这种增强只发生在相同的“本地”类型上node_modules as vuex。因为main-project有增强的Vue and dependency有原件,两者不符。

TS装载机

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

解决方法

我有一些想法如何解决或解决这个问题。尽管这些不一定是现成的解决方案,而是不同的方法和想法。

Add vuex to dependency

作为删除vuex from main-project这并不是一个真正的选择,我们唯一能做的就是同时实现这两个目标Vue接口匹配,包括vuex in dependency以及。

这里奇怪的是,我能够在我的最小示例中使此修复工作,但不能在原始项目中工作。我还没弄清楚这是为什么。

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

使用共享的node_modules

拥有一个共享的node_modules文件夹意味着两个项目使用相同的vue所以这个问题就消失了。根据您的要求,以共享相同的方式组织两个项目可能是一个很好的解决方案node_modules文件夹。您可能还想看看类似的工具Lerna https://lerna.js.org/ or 纱线工作区 https://yarnpkg.com/lang/en/docs/workspaces/这可以帮助解决这个问题。

Consume dependency作为一个包

你这么说dependency 还不是 [an] npm 依赖项。也许是时候将其合二为一了。与共享类似node_modules目录这将导致两个项目使用相同的vue安装应该可以解决问题。

进一步调查

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

TL;DR

main-project进口vuex while dependency没有。vuex增强了Vue接口使用声明合并 https://www.typescriptlang.org/docs/handbook/declaration-merging.html添加一个$store其财产。现在Vue来自一个项目不匹配Vue来自其他导致错误。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Vue&TypeScript:在项目目录之外的 TypeScript 组件中实现导入时如何避免错误 TS2345? 的相关文章

随机推荐