簡體   English   中英

Vue&TypeScript:在項目目錄之外的TypeScript組件中實施導入時,如何避免錯誤TS2345?

[英]Vue&TypeScript: how to avoid error TS2345 when import implemented in TypeScript component outside of project directory?

嘗試使用輔助組件(項目目錄外部)時出現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名稱中可以ui-kit-libary-in-development ,這還不是npm-dependency,因此它現在不在node_modules內部。

這完全是TypeScript錯誤。 盡管ts-loader會拋出此錯誤,但Webpack會構建我的項目,並且已編譯的JavaScript可以正常工作。 如果執行以下操作之一,此錯誤將消失:

  • SimpleCheckbox.vueSkipProjectInitializationStepPanel.ts移至同一目錄,並將其作為import SimpleCheckbox from './SimpleCheckbox.vue';
  • @Component({ template, components: { SimpleCheckbox } })刪除SimpleCheckbox並僅保留@Component({ template, components: {} }) (當然,在這種情況下,將不會呈現SimpleCheckbox ,但事實證明該問題是不在SkipProjectInitializationStepPanel )。
  • ui-kit-libary-in-development node_modules主項目的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>

我發現的所有線索都是該問題的注釋, 在組件裝飾器中設置組件會導致Typescript 2.4錯誤

側面組件應添加.d.ts才能正常運行。

尼克·梅辛

從這個線索中,產生了以下問題:

  • 在我的主項目或依賴項中應在哪里創建.d.ts 最有可能在主項目中,但是如果是這樣,為什么我可以在vuetify類的第三方庫中導入輔助組件? 因為那里有.d.ts
  • 我如何需要在.d.ts聲明新的Vue組件? 一些教程或示例?

賞金源文件

因為我無法重現此問題,並且我的項目仍然是原始項目(尚未獲得商業價值),所以我可以通過Google雲端硬盤( 下載zip存檔的鏈接)進行共享。 所有的node_modules都包括在內,只需在main-project目錄中運行npm run developmentBuild

如果您擔心潛在的病毒,也可以在此存儲庫中獲取源文件,但是由於它不包含node_modules ,因此,要進行復制,需要在main-projectdependency目錄中執行npm install

這已經是一個很長的答案。 如果您沒有時間閱讀所有內容,請在末尾找到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;

我們在這里看到的是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'.

如您所見,該錯誤消息非常難以閱讀,並且並未真正指出特定的問題。 因此,我改為着手開始降低項目的復雜性,以便更好地理解問題。

最小的例子

我將為您省去涉及很多試驗和錯誤的整個過程。 讓我們直接跳轉到結果。

結構體

├─ 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 {}

當使用npm run webpackmain-project運行此示例時,我們得到與以前完全相同的錯誤。 任務完成。

發生了什么?

當我刪除項目的各個部分以簡化為這個最小的示例時,我學到了一些非常有趣的東西。

您可能已經注意到我已添加到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類型利用聲明合並$store添加到vueVue接口。 看來,這增強只發生在同一個“本地”類型node_modulesvuex 因為main-project具有增強的Vuedependency具有原始Vue ,所以兩者不匹配。

TS裝載機

在我所有的測試過程中, tsc無法重現該問題。 顯然ts-loader所做的事情有所不同,導致了此問題。 它可能與使用Webpack進行模塊解析有關,也可能完全不同。 我不知道。

解決方法

我有一些想法可以解決或解決此問題。 盡管這些不一定是立即可用的解決方案,而是不同的方法和想法。

vuex添加到dependency

因為從main-project刪除vuex並不是一個真正的選擇,所以我們要做的唯一一個使兩個Vue接口都匹配的方法就是將vuex也包含在dependency中。

奇怪的是,我能夠在最小的示例中獲得此修復程序,但在原始項目中卻沒有。 我還不知道為什么。

除此之外,它還不是很優雅,您可能必須從main-project引用的每個文件中導入vuex

使用共享的node_modules

具有共享node_modules文件夾的手段這兩個項目使用相同的vue所以這個問題消失。 根據您的要求,將兩個項目共享一個相同的node_modules文件夾來組織它們可能是一個很好的解決方案。 您可能還想看一下LernaYarn工作區之類的工具,它們可以為您提供幫助。

dependency作為包使用

您說dependency 不是npm-dependency 也許是時候做到這一點了。 與共享的node_modules目錄類似,這將導致兩個項目使用相同的vue安裝,這將解決此問題。

進一步調查

如前所述,這僅在ts-loader發生。 也許可以在ts-loader固定或配置某些東西來避免此問題。

TL; DR

main-project導入vuex而沒有dependency vuex使用聲明合並來增強Vue接口, 並在其中添加$store屬性。 現在Vue從一個項目不匹配Vue其他導致錯誤。

正如@lukasgeiter所說,

顯然ts-loader所做的事情有所不同,導致了此問題。

如果將類型檢查過程委托給fork-ts-checker-webpack-plugin ,錯誤將消失。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM