Saltar al contenido

Vue y TypeScript: ¿cómo evitar el error TS2345 cuando la importación se implementa en el componente TypeScript fuera del directorio del proyecto?

Mateo, parte de este equipo de trabajo, nos ha hecho el favor de redactar esta reseña ya que domina perfectamente dicho tema.

Solución:

Esta se ha convertido en una respuesta bastante larga. Si no tienes tiempo para leerlo todo, hay un TL; DR al final.


Análisis

Mensaje de error

Al principio, realmente no entendía por qué TypeScript mencionaba VueClass y quejándose de la template propiedad. Sin embargo, cuando miré las definiciones de tipo para el Component decorador, las cosas se aclararon un poco:

vue-class-component/lib/index.d.ts (partes omitidas)

declare function Component(options: ComponentOptions & ThisType): >(target: VC) => VC;
// ...
declare function Component>(target: VC): VC;

Lo que podemos ver aquí es que Component tiene dos firmas. El primero se utilizará como lo hizo usted, y el segundo sin opciones (@Component class Foo).

Aparentemente, el compilador cree que nuestro uso no coincide con la primera firma, por lo que debe ser la segunda. Por lo tanto, terminamos con un mensaje de error con VueClass.

Nota: En la última versión (3.6.3), TypeScript realmente mostrará un error mejor, indicando tanto las sobrecargas como por qué no coinciden.

Mejor mensaje de error

Lo siguiente que hice fue comentar temporalmente la segunda declaración de función en main-project/node_modules/vue-class-component y, efectivamente, recibimos un mensaje de error diferente. El nuevo mensaje tiene 63 líneas, así que pensé que no tendría sentido incluirlo en esta publicación en su totalidad.

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 ' FunctionalComponentOptions> '.
  Property 'SimpleCheckbox' is incompatible with index signature.
    Type 'typeof SimpleCheckbox' is not assignable to type 'VueConstructor | FunctionalComponentOptions> | ComponentOptions> | AsyncComponentPromise | AsyncComponentFactory<...>'.
      Type 'typeof SimpleCheckbox' is not assignable to type 'VueConstructor'.
        Types of property 'extend' are incompatible.
          Type '{ (options?: import("/tmp/dependency/node_modules/vue/types/options").ThisTypedComponentOptionsWithArrayProps(options?: import("/tmp/main-project/node_modules/vue/types/options").ThisTypedComponentOptionsWithArrayProps

Como puede ver, el mensaje de error es bastante difícil de leer y realmente no apunta a un problema específico. Entonces, en lugar de eso, seguí adelante y comencé a reducir la complejidad del proyecto para comprender mejor el problema.

Ejemplo mínimo

Te ahorraré todo el proceso que implicó muchas pruebas y errores. Pasemos directamente al resultado.

Estructura

├─ main-project
│  ├─ node_modules // installed packages listed below (without sub-dependencies)
│  │     [email protected]
│  │     [email protected]
│  │     [email protected]
│  │     [email protected]
│  │     [email protected]
│  │     [email protected]
│  │     [email protected]
│  ├─ SkipProjectInitializationStepPanel.ts
│  ├─ tsconfig.json
│  └─ webpack.config.js
└─ dependency
   ├─ node_modules // installed packages listed below (without sub-dependencies)
   │     [email protected]
   └─ 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 

Al ejecutar este ejemplo desde main-project con npm run webpack, obtenemos exactamente el mismo error que antes. Misión cumplida.

¿Qué esta pasando?

Mientras eliminaba partes del proyecto para reducirlo a este ejemplo mínimo, aprendí algo muy interesante.

Puede que hayas notado el import 'vuex' He agregado a SkipProjectInitializationStepPanel.ts. En el proyecto original vuex es, por supuesto, importado de diferentes lugares (p. ej. main-project/Source/ProjectInitializer/Store/Store.ts) pero eso no es importante para reproducir el problema. La parte crucial es que main-project importaciones vuex y dependency no.

Para saber por qué importar vuex causa este problema, tenemos que mirar las definiciones de tipo de vuex.

vuex/types/index.d.ts (primeras líneas)

import _Vue,  WatchOptions  from "vue";

// augment typings of Vue.js
import "./vue";

import  mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers  from "./helpers";

Aquí nos interesa el segundo import. El comentario ya nos da una pista: aumentar tipificaciones de Vue.js.

vuex/types/vue.d.ts (partes omitidas)

import  Store  from "./index";

// ...

declare module "vue/types/vue" 
  interface Vue 
    $store: Store;
  

Y aquí tenemos al culpable. los vuex los tipos hacen uso de la combinación de declaraciones para agregar $store al Vue interfaz de vue. Parece que este aumento solo ocurre con los tipos "locales" en el mismo node_modules como vuex. Porque main-project tiene el aumentado Vue y dependency tiene el original, los dos no coinciden.

Cargador TS

Durante todas mis pruebas no pude reproducir el problema con solo tsc. Aparentemente ts-loader hace algo diferente que causa este problema. Podría tener que ver con el uso de Webpack para la resolución del módulo o podría ser algo completamente diferente. No sé.

Enfoques de solución

Tengo algunas ideas sobre cómo resolver o solucionar este problema. Aunque estas no son necesariamente soluciones listas para usar, sino enfoques e ideas diferentes.

Agregar vuex para dependency

Como remover vuex de main-project no es realmente una opción, lo único que podemos hacer para que tanto Vue las interfaces coinciden, se incluye vuex en dependency así como.

Lo extraño aquí es que pude hacer que esta solución funcionara en mi ejemplo mínimo, pero no en el proyecto original. No he descubierto por qué es así.

Además de eso, no es muy elegante y es posible que deba importar vuex de cada archivo al que hace referencia main-project.

Utilice un compartido node_modules

Tener un compartido node_modules carpeta significa que ambos proyectos usan el mismo vue entonces este problema desaparece. Dependiendo de sus requisitos, puede ser una buena solución organizar los dos proyectos de manera que compartan el mismo node_modules carpeta. También es posible que desee echar un vistazo a herramientas como los espacios de trabajo de Lerna o Yarn que pueden ayudar con esto.

Consumir dependency como un paquete

Tu dices eso dependencyno es [an] npm-dependency todavía. Tal vez sea el momento de convertirlo en uno. Similar a un compartido node_modules directorio, esto daría como resultado que ambos proyectos usen el mismo vue instalación que debería solucionar el problema.

Investigar más a fondo

Como se mencionó anteriormente, esto solo ocurre con ts-loader. Quizás hay algo que se puede arreglar o configurar en ts-loader para evitar este problema.

TL; DR

main-project importaciones vuex tiempo dependency no lo hace. vuex aumenta el Vue interfaz usando declaración de fusión agregando un $store propiedad a ella. Ahora el Vue de un proyecto no coincide Vue de otro causando un error.

Si estás contento con lo expuesto, eres capaz de dejar una división acerca de qué te ha impresionado de esta división.

¡Haz clic para puntuar esta entrada!
(Votos: 2 Promedio: 5)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *