尝试使用辅助组件(项目目录外部)时出现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';
导入SimpleCheckbox
中删除@Component({ template, components: { SimpleCheckbox } })
并仅保留@Component({ template, components: {} })
(当然,在这种情况下SimpleCheckbox
将不会被呈现,但事实证明问题不在SkipProjectInitializationStepPanel
中)。 ui-kit-libary-in-development
移至主项目的node_modules
,并从node_modules
中删除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-libary/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问题中的此评论:Side components should add .d.ts for it to work AFAIK.
从这个线索中,产生了以下问题:
.d.ts
-在我的主项目或依赖项中?最有可能在主项目中,但是如果是这样,为什么我可以在vuetify
之类的第三方库中导入辅助组件?因为那里有.d.ts
! .d.ts
中声明新的Vue组件?一些教程或示例? 赏金源文件
因为我无法重现此问题,并且我的项目仍然是原始项目(尚未获得商业值(value)),所以我可以通过Google云端硬盘(link for downloading zip archive)共享它。包括所有
node_modules
,只需在npm run developmentBuild
目录中运行main-project
。如果您担心潜在的病毒,也可以在this repository中获取源文件,但是由于它不包含
node_modules
,因此,要进行复制,需要在npm install
和main-project
目录中都执行dependency
。
最佳答案
这已经是一个很长的答案。如果您没有时间阅读全部内容,则在结尾处有一个 TL; DR。 Note: In the latest version (3.6.3), TypeScript will actually display a better error, stating both overloads and why they don't match.
分析
错误信息
最初,我不太了解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
)。
显然,编译器认为我们的用法与第一个签名不匹配,因此它必须是第二个签名。因此,我们最终收到一 strip 有VueClass<Vue>
的错误消息。
更好的错误信息
我要做的下一件事是暂时注释掉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-project
从npm run webpack
运行此示例时,我们将获得与之前完全相同的错误。任务完成。
发生了什么?
当我删除项目的各个部分以简化为这个最小的示例时,我学到了一些非常有趣的东西。
您可能已经注意到我添加到import 'vuex'
的SkipProjectInitializationStepPanel.ts
了。在原始项目中,vuex
当然是从不同的地方导入的(例如main-project/Source/ProjectInitializer/Store/Store.ts
),但这对于重现该问题并不重要。关键部分是 main-project
导入vuex
,而dependency
不导入。
为了找出为什么导入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
添加到Vue
的vue
接口(interface)。似乎这种增加仅发生在与node_modules
相同vuex
中的“本地”类型上。由于main-project
具有增强的Vue
,而dependency
具有原始的,因此两者不匹配。
TS装载机
在我所有的测试过程中,仅使用tsc
就无法重现该问题。显然ts-loader
所做的事情有所不同,导致了此问题。它可能与使用Webpack进行模块解析有关,或者可能完全不同。我不知道。
解决方法
我对如何解决或解决此问题有一些想法。尽管这些不一定是立即可用的解决方案,而是不同的方法和想法。
将vuex
添加到dependency
由于从vuex
中删除main-project
并不是真正的选择,因此我们要使两个Vue
接口(interface)都匹配,唯一可以做的就是在vuex
中也包括dependency
。
奇怪的是,我能够在最小的示例中获得此修复程序,但在原始项目中却没有。我还不知道为什么会这样。
除此之外,它不是很优雅,您可能必须从vuex
引用的每个文件中导入main-project
。
使用共享的node_modules
具有共享的node_modules
文件夹意味着两个项目都使用相同的vue
,因此该问题就消失了。根据您的要求,将两个项目共享为相同的node_modules
文件夹来组织它们可能是一个很好的解决方案。您可能还想看一看Lerna或Yarn workspaces之类的工具,它们可以为您提供帮助。
将dependency
打包使用
您说dependency
还不是[pmn]依赖项。也许是时候做到这一点了。与共享的node_modules
目录类似,这将导致两个项目使用相同的vue
安装,这应该可以解决此问题。
进一步调查
如前所述,只有ts-loader
会发生这种情况。也许可以在ts-loader
中固定或配置某些东西来避免此问题。
TL; DRmain-project
导入vuex
,而dependency
不导入。 vuex
通过使用declaration merging向其添加Vue
属性来增强$store
接口(interface)。现在,一个项目中的Vue
与其他项目中的Vue
不匹配,从而导致错误。
关于typescript - Vue&TypeScript : how to avoid error TS2345 when import implemented in TypeScript component outside of project directory?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57355411/