【问题标题】:Vue&TypeScript: how to avoid error TS2345 when import implemented in TypeScript component outside of project directory?Vue&TypeScript:在项目目录外的 TypeScript 组件中实现导入时如何避免错误 TS2345?
【发布时间】:2019-12-12 18:56:44
【问题描述】:

尝试使用侧组件(项目目录外)时出现以下 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-development的名字来看,这还不是npm-dependency,所以暂时不在node_modules里面。

这完全是 TypeScript 错误;尽管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(如果不删除,则不会有任何变化)。

很遗憾,我无法重现此问题。出于某种原因,以下尝试复制作品没有错误:

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中的这条评论:

侧面组件应添加 .d.ts 以使其正常工作。

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-project和@987654356中执行npm install @ 目录。

【问题讨论】:

  • " 是在我使用 TypeScript loader 运行 Webpack 时在控制台中输出的。" 你是如何运行它的?命令是什么,在什么文件夹下?
  • @acdcjunior,目录:“path/where/you/unzipped/**main-project**”,命令:npm run developmentBuild

标签: typescript vue.js vue-component typescript-decorator typescript-definitions


【解决方案1】:

这已成为一个很长的答案。如果您没有时间阅读全部内容,那么末尾有一个TL;DR


分析

错误信息

起初我并不真正理解为什么 TypeScript 会提到 VueClass&lt;Vue&gt; 并抱怨 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;

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

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

注意:在最新版本 (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)
│  │     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

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-projectnpm run webpack 运行此示例时,我们得到与以前完全相同的错误。任务完成。

发生了什么?

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

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

要找出导入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 感兴趣。该评论实际上已经给了我们一个提示:Vue.js 的增强类型

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

import { Store } from "./index";

// ...

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

我们找到了罪魁祸首。 vuex 类型利用declaration merging$store 添加到vueVue 接口。似乎这种增强只发生在与vuex 相同的node_modules 中的“本地”类型。因为main-project 有增强的Vuedependency 有原始的,所以两者不匹配。

TS 加载器

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

解决方法

我对如何解决或解决此问题有一些想法。虽然这些不一定是现成的解决方案,而是不同的方法和想法。

vuex 添加到dependency

由于从main-project 中删除vuex 并不是一个真正的选择,我们唯一可以使Vue 接口匹配的方法是将vuex 包含在dependency 中。

这里奇怪的是,我能够在我的最小示例中得到这个修复,但在原始项目中却没有。我还没弄清楚为什么会这样。

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

使用共享的node_modules

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

以包的形式使用dependency

你说dependency 还不是[an] npm-dependency。也许是时候把它变成一个了。与共享的node_modules 目录类似,这将导致两个项目使用相同的vue 安装,这应该可以解决问题。

进一步调查

如前所述,这只发生在ts-loader 上。 也许可以在ts-loader 中修复或配置一些东西来避免这个问题。

TL;DR

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

【讨论】:

  • 你的答案假装是论文。非常深入的研究;我目前的知识还不够。仅仅“谢谢”是不够的,但不幸的是,我的赏金没有赢家。我相信卡瓦诺先生会给你声誉奖。
【解决方案2】:

正如@lukasgeiter 所说,

显然 ts-loader 做了一些不同的事情导致了这个问题。

如果将类型检查过程委托给fork-ts-checker-webpack-plugin,错误将消失。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-05-27
    • 2019-04-28
    • 1970-01-01
    • 1970-01-01
    • 2021-04-20
    • 2020-03-11
    • 1970-01-01
    • 2017-07-01
    相关资源
    最近更新 更多