
Думаю, что сейчас необходимость статической типизации фронтенд приложений не вызывает сомнений, не буду описывать все ее плюсы и минусы, так как на Хабр уже неоднократно обсуждалась данная тема. В данной статье бы хотел предложить решение, которое позволит выполнять проверку типов при создании HTML разметки во Vue.
Какие проблемы существуют?
Типизация — это одна из главных проблем Vue.
В: Каковы самые слабые места Vue?
O: На данный момент, наверное, недружественность к типизации. Наш API разрабатывался без планирования поддержки типизированных языков (типа TypeScript), но мы сделали большие улучшения в 2.5.
из статьи Создатель Vue.js отвечает Хабру
Для типизации скрипта во Vue используется достаточно популярное решение — это библиотека vue-class-component, но вопросы по типизации шаблона так и остаются открытыми.
Какую типизацию для шаблонов хотелось бы получить?
Для удобной работы с шаблонами необходимо, чтобы выполнялись следующие условия:
-
Проверка используемых переменных
-
Проверка атрибутов для HTML элементов
-
Проверка событий(должен определяться тип для объекта события) для HTML элементов
-
Проверка параметров компонента
-
Проверка событий(должен определяться тип для объекта события) компонентов
-
Проверка слотов(должен определяться тип для параметров слота)
Поиск решения
Решений для типизации шаблонов я не нашел, поэтому стал рассматривать другие варианты.
Кроме шаблонов, Vue так же поддерживает render — функции и jsx. TypeScript поддерживает проверку типов для jsx, поэтому я решил подключить его в проект и посмотреть, что из этого получиться.
Подключение jsx позволило решить ряд проблем, но у компонентов не определялись параметры, слоты и события, исправить это мне помогла библиотека vue-tsx-support.
Подключение и использование данной библиотеки достаточно подробно описано у них на странице, поэтому сюда не буду дублировать эту информацию.
События во vue-tsx-support
Единственное, что мне не понравилось в данной библиотеке, так это работа с событиями. Вместо использования this.$emit они предлагают tsx.emitOn
import * as tsx from "vue-tsx-support"; tsx.emitOn(this, "onRowClicked", { item, index });
Проблема типизации событий связана с тем, что в jsx названия событий начинаются с префикса «on» (во vue создаем событие rowClicked, а в jsx подписываемся на onRowClicked)
В функции emitOn происходит преобразование названия события из jsx стиля к стандартному vue стилю(«onRowClicked» => «rowClicked»)
function emitOn<Events, Name extends string & keyof Events>( vm: Vue & { _tsx: DeclareOnEvents<Events> }, name: Name, ...args: Parameters<EventHandler<Events[Name]>> ) { vm.$emit( name.replace(/^on[A-Z]/, v => v[2].toLowerCase()), ...args ); }
Решить проблему типизации событий можно и без данной функции. Для этого нужно переопределить типы для Component в библиотеке vue-tsx-support, добавив туда четвертый параметр VueEvents.
declare module "vue-tsx-support/lib/api" { interface Component< Props, PrefixedEvents = {}, ScopedSlotArgs = {}, VueEvents = {} > extends Vue { _tsx: TsxComponentTypeInfo<{}, Props, PrefixedEvents, {}>; $scopedSlots: InnerScopedSlots<ScopedSlotArgs>; $emit<T extends string & keyof VueEvents>( event: T, ...args: Parameters<EventHandler<VueEvents[T]>> ): this; } }
Во VueEvents будут указываться названия событий в том виде, в котором они используются во vue(события в jsx стиле это PrefixedEvents).
Для того чтобы не делать отдельные интерфейсы для vue событий и jsx событий, я использую тип который выполняет данные преобразования(jsxEventMapper).
type ReverseMap<T extends Record<keyof T, keyof any>> = { [P in T[keyof T]]: { [K in keyof T]: T[K] extends P ? K : never; }[keyof T]; }; type ReverseJsxEvent<T> = ReverseMap<{ [key in keyof T & string]: `on${Capitalize<key>}` }>; type jsxEventMapper<T> = { [rootKey in keyof ReverseJsxEvent<T>]: T[ReverseJsxEvent<T>[rootKey]]; };
В конечном итоге компонент выглядит следующим образом.
import * as tsx from 'vue-tsx-support'; import { VNode } from 'vue'; import Component from 'vue-class-component'; import { jsxEventMapper } from './jsxHelpers'; interface IProps { myProp1: string; myProp2: string; } interface IEvents { myEvent: number; } interface ISlots { mySlot: string; } @Component({}) export default class MyComponent extends tsx.Component< IProps, jsxEventMapper<IEvents>, ISlots, IEvents > { private render(): VNode {} }
IntelliSense в Visual Studio Code
Так же хотел бы отметить, что Visual Studio Code, отображает подсказки при работе с такими компонентами, что существенно улучшает процесс разработки.



ссылка на оригинал статьи https://habr.com/ru/post/651853/
Добавить комментарий