typescript - Vue&TypeScript : how to avoid error TS2345 when import implemented in TypeScript component outside of project directory?

标签 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>'.
我的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.

    Nick Messing


    从这个线索中,产生了以下问题:
  • 我应该在哪里创建.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 installmain-project目录中都执行dependency

    最佳答案

    这已经是一个很长的答案。如果您没有时间阅读全部内容,则在结尾处有一个 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)。
    显然,编译器认为我们的用法与第一个签名不匹配,因此它必须是第二个签名。因此,我们最终收到一 strip 有VueClass<Vue>的错误消息。

    Note: In the latest version (3.6.3), TypeScript will actually display a better error, stating both overloads and why they don't match.


    更好的错误信息
    我要做的下一件事是暂时注释掉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运行此示例时,我们将获得与之前完全相同的错误。任务完成。
    发生了什么?
    当我删除项目的各个部分以简化为这个最小的示例时,我学到了一些非常有趣的东西。
    您可能已经注意到我添加到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添加到Vuevue接口(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文件夹来组织它们可能是一个很好的解决方案。您可能还想看一看LernaYarn 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/

    相关文章:

    node.js - 如何用qInput实现qDate范围选择?

    javascript - Vue.js - 单击按钮时迭代数据对象

    vue.js - Vue - 使用 ref 访问嵌套的 child

    javascript - 如何在 VueJS 中处理插槽的 onClick 事件

    javascript - Prop 更改时子组件不会更新

    javascript - Typescript - 从数组项的通用类型创建组合类型

    java - Typescript 中的工厂模式并仅公开工厂类

    javascript - 如何将响应式(Reactive)表单字段映射到线性数组?

    angularjs - 如何结合 Play Framework 和angular2?

    javascript - 在 :value of <option> tag in vuejs 中使用 split()