Думаю, что сейчас необходимость статической типизации фронтенд приложений не вызывает сомнений, не буду описывать все ее плюсы и минусы, так как на Хабр уже неоднократно обсуждалась данная тема. В данной статье бы хотел предложить решение, которое позволит выполнять проверку типов при создании 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, отображает подсказки при работе с такими компонентами, что существенно улучшает процесс разработки.
antyas
А еще лучше будет перейти на vue3, где типизация шаблонов из коробки. И еще куча приятных вещей улучшающих DX. По трудозатратам выйдет не больше чем тащить в проект jsx и vue-class-component (причем оба гадость еще та).
SergeyPeskov Автор
Разве во Vue 3 есть решена проблема проверки типов в шаблоне?
PGGSMR63
Да, сам Vue3 переписан на TS, так что там с поддержкой типов все в разы лучше, плюсом к тому Evan уже объявил Vue3 дефолтной версией Vue, так что если планируете дальнейшее развитие проекта, лучше будет его переписать на Vue3.
js_n00b
Во Vue 3 действительно с типизацией все гораздо лучше, но вопрос немного не о том. Сам недавно столкнулся с проблемой, когда я поменял имя свойства объекта и его интерфейс, но забыл поменять на новое значение в template. И долго сидел не понимая почему у меня ничего не работает. TS не сообщил об ошибке.
SergeyPeskov Автор
Да, с типизацией скрипта во Vue 3 проблем быть не должно, но я говорю именно про шаблон(<template></template>), информации о том, что данную проблему решили я не нашел.