
JavaScript es uno de los idiomas con escritura dinámica. Dichos lenguajes son convenientes para el desarrollo rápido de aplicaciones, pero cuando varios equipos se encargan del desarrollo de un gran proyecto, es mejor elegir una de las herramientas de verificación de tipos desde el principio.
Puede comenzar a desarrollar código TypeScript o incluirlo en un proyecto Flow. TypeScript es una versión compilada de JavaScript desarrollada por Microsoft. Flow, a diferencia de TypeScript, no es un lenguaje, sino una herramienta que le permite analizar código y verificar tipos. Puede encontrar muchos artículos y videos en la red sobre estos enfoques, así como una guía sobre cómo comenzar a usar la escritura. En este artículo nos gustaría decirle por qué Flow no nos conviene y cómo comenzamos a cambiar a Typecript.
Un poco de historia
En 2016, comenzamos a desarrollar un cliente web basado en React / Redux para nuestro sistema ECM. Se seleccionó Flow para probar la escritura por los siguientes motivos:
- React and Flow son productos de la misma empresa Facebook.
- El flujo se desarrolló más activamente.
- Flow se integra fácilmente en el proyecto.
Pero el proyecto creció, aumentó el número de equipos de desarrollo y aparecieron varios problemas al usar Flow:
- La verificación de tipo de fondo Flow utilizó demasiados recursos de PC. Como resultado, algunos desarrolladores lo desactivaron y comenzaron a verificar según sea necesario.
- Hubo situaciones en las que tomó tanto tiempo alinear el código con Flow como escribir el código en sí.
- El código comenzó a aparecer en el proyecto, necesario solo para pasar la prueba de flujo. Por ejemplo, doble comprobación de nulo:
foo() { if (this.activeFormContainer == null) { return; }
- La mayoría de los desarrolladores utilizaron el editor de código de Visual Studio Code, en el que Flow no tiene un soporte tan bueno como TypeScript. La finalización automática (IntelliSense) no siempre funcionó durante el desarrollo, y la navegación de código era inestable. Me gustaría tener la misma comodidad de desarrollo que cuando escribo en C # en Visual Studio.
Algunos desarrolladores tuvieron la idea de intentar cambiar a TypeScript. Para probar la idea de la transición y convencer a la gerencia, decidimos probar el prototipo.
Prototipo
Queríamos probar dos ideas sobre prototipos:
- Intenta traducir todo el proyecto.
- Configure el proyecto para que pueda usar Flow y Typecript en paralelo.
Para la primera idea, se necesitaba una utilidad que convirtiera todos los archivos del proyecto. En la red encontramos uno de estos. A juzgar por la descripción, ella podría traducir la mayoría, pero algunos de los cambios tendrían que ser editados por nosotros mismos, o la utilidad en sí debería agregarse. Pudimos convertir un proyecto de prueba con una pequeña cantidad de archivos. Pero el proyecto real no se pudo compilar, habría sido necesario editar demasiados archivos. Decidimos no continuar en esta dirección, ya que:
- ¡Aún quedaba mucho por hacer! Y mientras finalizaremos el proyecto, los equipos restantes continuarán desarrollando nuevas funcionalidades, reparando errores, escribiendo pruebas. Además, tomaría mucho tiempo fusionar archivos.
- Incluso si tradujimos el proyecto de esta manera, ¡cuánto trabajo tendrían que hacer nuestros evaluadores!
Aunque abandonamos esta opción, adquirimos experiencia útil en ella. Quedó claro la cantidad aproximada de trabajo que debe hacerse para traducir cada archivo. Así es como se ve la traducción de un componente React simple.

Como puede ver, no hay muchos cambios. Básicamente, son los siguientes:
- eliminar // @ flow;
- reemplazar el tipo con una interfaz más familiar;
- agregar modificadores de acceso;
- reemplace los tipos con tipos de bibliotecas ts (del ejemplo en la imagen: controladores de eventos y los eventos mismos).
La implementación de la segunda idea permitiría un mayor desarrollo, pero ya en TypeScript, y en segundo plano para traducir lentamente la base de código existente. Esto proporcionó varias ventajas:
- Fácil de traducir, sin temor a perderse algo.
- Fácil de probar
- Fácil de combinar cambios.
Pero no estaba completamente claro si el proyecto podía configurarse para funcionar con dos tipos de tipeo en paralelo. Una búsqueda en Internet no condujo a nada concreto, por lo que comenzaron a resolverlo ellos mismos. En teoría, el analizador de flujo solo verifica los archivos con la extensión js / jsx y que contienen un comentario:
Para el compilador TypeScript, los archivos deben tener la extensión ts / tsx. De lo que se deduce que ambos enfoques para escribir deben funcionar simultáneamente y no interferir entre sí. En base a esto, configuramos el entorno del proyecto. Utilizando la experiencia del primer prototipo, tradujimos un par de archivos. Compilé el proyecto, lancé el cliente, ¡todo funcionó como antes!
Luz verde
Y un buen día: el día de la planificación del sprint, nuestro equipo tiene una historia de usuario "Comience a cambiar a TypeScript" en la cartera de pedidos, con la siguiente lista de trabajos:
- Configurar webpack.
- Configurar tslint.
- Configure un entorno de prueba.
- Traducir archivos a TypeScript.
Configuración de paquete web
El primer paso es enseñarle al paquete web cómo manejar archivos con la extensión ts / tsx. Para hacer esto, agregamos una regla a la sección de reglas del archivo de configuración. Originalmente usado ts-loader:
Para acelerar el ensamblaje, la verificación de tipos estaba desactivada:
transpileOnly: true
, porque El IDE ya indica errores al escribir código.
Pero cuando comenzamos a traducir nuestras acciones de Redux, quedó claro que necesitaban el
complemento babel-plugin-transform-class-display-name para funcionar. Este complemento agrega una propiedad estática en todas las clases. Después de la traducción, solo se procesaron las acciones ts-loader, y esto no permitió que se les aplicaran los complementos de babel. Como resultado, abandonamos ts-loader y extendimos la regla existente para js / jsx agregando
babel / preset-typescript:
Para que el compilador TypeScript funcione correctamente, debe agregar el archivo de configuración tsconfig.json, que se tomó de la documentación.
Configurar Tslint
El código escrito con Flow también se verificó con eslint. TypeScript tiene su contraparte, tslint. Inicialmente, quería transferir todas las reglas de eslint a tslint. Se intentó sincronizar las reglas a través del complemento tslint-eslint-rules, pero la mayoría de las reglas no son compatibles. También es posible usar eslint para verificar los archivos ts usando typecript-eslint-parser. Pero, desafortunadamente, solo se puede conectar un analizador a eslint. Si usa solo ts-parser para todo tipo de archivos, aparecen muchos errores extraños tanto en los archivos js como en los ts. Como resultado, utilizamos el conjunto de reglas recomendado, ampliado a nuestros requisitos:
// tslint.json "extends": ["tslint:recommended", "tslint-react"]
Traducir un archivo a TypeScript
Ahora todo está listo y puede comenzar a traducir archivos. Para empezar, decidimos transferir un pequeño componente React, que se utiliza durante todo el proyecto. La elección recayó en el componente "Botón".

Encontramos un problema durante el proceso de traducción: no todas las bibliotecas de terceros tienen mecanografía TypeScript, por ejemplo, bem-cn-lite. En el recurso
TypeSearch de Microsoft, no se pudo encontrar la biblioteca de tipos. para casi todas las bibliotecas necesarias, encontramos y conectamos bibliotecas de tipo ts. Una solución fue conectarse a través de require:
const b = require('bem-cn-lite');
Pero al mismo tiempo, el problema con la falta de tipos no se resolvió. Por lo tanto, generamos un "código auxiliar" para los tipos nosotros mismos, utilizando la utilidad
dts-gen :
dts-gen -m bem-cn-lite
La utilidad generó un archivo con la extensión * .d.ts. El archivo se colocó en la carpeta @types y configuró tsconfig.json:
// tsconfig.json "typeRoots": [ "./@types", "./node_modules/@types" ]
Luego, por analogía con el prototipo, tradujimos el componente. Compilé el proyecto, lancé el cliente, ¡todo funcionó! Pero las pruebas se rompieron.
Configuración del entorno de prueba
Para probar la aplicación, usamos Storybook y Mocha.
Storybook se usa para pruebas de regresión visual (
artículo ). Al igual que el proyecto en sí, está construido con webpack y tiene su propio archivo de configuración. Por lo tanto, para trabajar con archivos ts / tsx, tenía que configurarse por analogía con la configuración del proyecto en sí.
Si bien utilizamos ts-loader para construir el proyecto, dejamos de ejecutar las pruebas de Mocha. Para resolver este problema, agregue ts-node al entorno de prueba:
// mocha.opts --require @babel/polyfill --require @babel/register --require test/index.js --require tsconfig-paths/register --require ts-node/register/transpile-only --recursive --reporter mochawesome --reporter-options reportDir=../../bin/TestResults,reportName=js-test-results,inlineAssets=true --exit
Pero después de cambiar a Babel, puedes deshacerte de él.
Los problemas
En el proceso de traducción, encontramos una gran cantidad de problemas de diversos grados de complejidad. Se relacionaron principalmente con nuestra falta de experiencia con TypeScript. Aquí hay algunos de ellos:
- Importar componentes / funciones de diferentes tipos de archivos.
- Traducción de componentes de orden superior.
- Historia de pérdida de cambio.
Importar componentes / funciones de diferentes tipos de archivos
Al usar componentes / funciones de diferentes tipos de archivos, se hizo necesario especificar la extensión del archivo:
import { foo } from './utils.ts'
Esto le permite agregar extensiones válidas a los archivos de configuración webpack y eslint:
Traducción de componentes de orden superior
De todos los tipos de archivos, la traducción del Componente de orden superior (HOC) causó la mayoría de los problemas. Esta es una función que toma un componente en la entrada y devuelve un nuevo componente. Se utiliza principalmente para reutilizar la lógica, por ejemplo, puede ser una función que agrega la capacidad de seleccionar elementos:
const MyComponentWithSeletedItem = withSelectedItem(MyComponent);
O la conexión más famosa, de la biblioteca Redux. Escribir estas funciones no es trivial y requiere conectar una biblioteca adicional para trabajar con tipos. No describiré el proceso de traducción en detalle, ya que puede encontrar muchos manuales sobre el tema en la red. En resumen, el problema es que dicha función es abstracta: cualquier componente con cualquier conjunto de propiedades puede aceptar entradas. Puede ser un componente Button con título y propiedades onClick o un componente Picture con propiedades alt e imgUrl. No conocemos de antemano el conjunto de estas propiedades; solo se conocen aquellas propiedades que la función agrega. Para que el compilador TypeScript no maldiga cuando usa componentes obtenidos con la ayuda de tales funciones, es necesario "cortar" las propiedades que la función agrega del tipo de retorno.
Para hacer esto, necesitas:
- Tire estas propiedades a la interfaz:
interface IWithSelectItem { selectedItem: number; handleSelectedItemChange: (id: number) => void; }
- Elimine todas las propiedades que ingresan a la interfaz IWithSelectItem de la interfaz del componente. Para hacer esto, puede usar la operación Diff <T, U> de la biblioteca de tipos de utilidad .
React.ComponentType<Diff<TPropsComponent, IWithSelectItem>>
Historia de pérdida de cambio
Para trabajar con fuentes, por ejemplo, revisión de código, utilizamos Team Foundation Server. Al traducir archivos, nos encontramos con una característica desagradable. En lugar de un archivo modificado, aparecen dos en el grupo de solicitudes:
- remoto: la versión anterior del archivo;
- creado - nueva versión.

Este comportamiento se observa si hay muchos cambios en el archivo (similitud <50%), por ejemplo, para archivos pequeños. Para resolver este problema, intentamos usar:
- comando git mv
- ejecute dos confirmaciones: la primera está cambiando la extensión del archivo, la segunda es con correcciones inmediatas.
Pero, desafortunadamente, ambos enfoques no nos ayudaron.
Resumen
Use Flow o TypeScript: todos deciden por sí mismos, ambos enfoques tienen sus pros y sus contras. Elegimos TypeScript para nosotros mismos. Y usted estaba convencido de su propia experiencia: si elige uno de los enfoques y de repente se da cuenta, incluso después de tres años, que no le conviene, siempre puede cambiarlo. Y para una transición más fluida, puede configurar el proyecto, como nosotros, para que funcione en paralelo.
Al momento de escribir, todavía no hemos cambiado completamente a TypeScript, pero ya hemos reescrito la parte principal: el "núcleo" del proyecto. En la base del código, puede encontrar ejemplos de la traducción de todo tipo de archivos, desde un componente de reacción simple hasta componentes de orden superior. Además, la capacitación se realizó entre todos los equipos de desarrollo, y ahora cada equipo, como parte de su tarea, transfiere parte del proyecto a esas tareas.
Planeamos completar la transición antes de fin de año, traducir pruebas y un libro de cuentos, y tal vez incluso escribir algunas de nuestras reglas tslint.
De acuerdo con mis sentimientos personales, puedo decir que el desarrollo comenzó a tomar menos tiempo, la verificación de tipos se realiza sobre la marcha, sin cargar el sistema, y los mensajes de error para mí personalmente se volvieron más comprensibles.