Как стать автором
Обновить

Проверка типов во Vue при создании html. Меняем шаблоны на jsx

Время на прочтение3 мин
Количество просмотров3.6K

Думаю, что сейчас необходимость статической типизации фронтенд приложений не вызывает сомнений, не буду описывать все ее плюсы и минусы, так как на Хабр уже неоднократно обсуждалась данная тема. В данной статье бы хотел предложить решение, которое позволит выполнять проверку типов при создании 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, отображает подсказки при работе с такими компонентами, что существенно улучшает процесс разработки.

Теги:
Хабы:
+2
Комментарии6

Публикации

Истории

Работа

Ближайшие события