Pull to refresh

TypeScript: общие впечатления

JavaScript *TypeScript *
Sandbox
Думаю, многие из вас знают, что сейчас существует обилие различных языков (или инструментов), которые позволяют писать код компилируемый в JavaScript. Это CoffeeScript, Dart, GorillaScript и другие (довольно большой список можно найти здесь). Недавно я решил познакомиться с одним из представителей этого списка — языком программирования под названием TypeScript.

Я расскажу о личном опыте — почему решил попробовать именно его, как начать работать с ним, и какие выводы сделал, поработав с ним несколько дней.

Если вам это интересно — пожалуйста, заходите под кат.

Небольшая выжимка в качестве описания этого языка


  • Язык со строгой типизацией
  • Код написанный на TypeScript компилируется в JavaScript
  • Язык обратно совместим с JavaScript — если вы скормите компилятору чистый JavaScript, компилятор выплюнет вам ваш же JS, и не скажет что это ошибка. Можно писать смешанный код (например, модули/методы используя синтаксис TypeScript, а реализацию методов без типизации на чистом JS) — это тоже будет валидно.
  • Язык разработан компанией Microsoft.


Какие возможности открывает язык


  • Система для работы с модулями/классами — можно создать интерфейсы, модули, классы;
  • Можно наследовать интерфейсы (в том числе множественное наследование), классы;
  • Можно описывать собственные типы данных;
  • Можно создавать универсальные-интерфейсы (generic interfaces);
  • Можно описать тип переменной (или свойств объекта), или описать каким интерфейсом должен обладать объект на который ссылается переменная;
  • Можно описать сигнатуру метода.


Конечно, это далеко не все — это лишь основные моменты, которые я выделил.

Основные ссылки




Почему стоит попробовать TypeScript?


Размышляя о том, почему имеет смысл попробовать поработать на TypeScript, я выделил несколько основных пунктов:

  • Возможность жестко описать каждый элемент приложения — это, вероятно, исключает возможность неверной реализации или некорректного вызова методов; Заставляет разработчиков продумать логику (например, в методах) до самой реализации; Не дает возможность изменить один кусок приложения, сломав другой;
  • Возможность описать область видимости свойств класса — еще один барьер, который ограничивает разработчика от совершения ошибок;
  • Из-за жесткой архитектуры, возможно, необходимо писать меньше тестов — все параметры методов жестко описаны, и если код скомпилировался, тогда, вероятно, каждый вызов является валидным, и не требует дополнительной проверки;
  • Можно настроить проект таким образом, что любой некомпилируемый код (фактически, код с синтаксической ошибкой) нельзя будет закоммитить. Случаи в которых разработчик позволяет себе закоммитить сломанный код в обход проверок перед коммитом не рассматриваем;
  • Многие конструкции в TypeScript имеют жесткий формат, поэтому многие из ошибок форматирования кода (в соответствии каким-то принятым в коллективе нормам) исключены. Я думаю что это плюс, потому что процесс ревью кода часто выявляет ошибки форматирования, на которые тратится ценное время. Если это время можно потратить на другие более полезные активности — это уже плюс.


С другой стороны, есть ряд минусов:

  • Чтобы использовать какой-то внешний инструмент (читай «библиотеку» или «фреймворк»), тогда сигнатуру каждого из методов каждого модуля этого инструмента необходимо описать, чтобы компилятор не выбрасывал ошибки (он просто не будет знать про ваш инструмент). Если это популярный инструмент, тогда, вероятнее всего, описание интерфейсов можно найти в этом репозитории. Если нет — придется описывать самому;
  • Вероятно, самый большой минус (возможно, временный) — порог вхождения и количество специалистов на рынке. Сейчас почти нет специалистов которые знают этот язык. Это плохо, потому что любой солидный проект со временем переходит на этап поддержки, и в случае потери специалистов, найти им замену будет сложнее. Возможно, я ошибаюсь, но я пришел к этому выводу опираясь на статистику Github (ссылка) — создано лишь ~6k репозиториев с TypeScript кодом против ~1.4kk на JavaScript;
  • На разработку тратится больше времени, в сравнении с JavaScript. Это вызвано тем, что помимо реализации класса необходимо описать все задействованные интерфейсы, сигнатуры методов.


Начинаем работать


Перехожу к небольшим заметкам, которые я оставлял в уме по мере работы с TypeScript.

Сборка


Прежде всего, было необходимо написать сборщик тестового проекта. Есть несколько NPM пакетов для gulp которые позволяют компилировать TypeScript код в JavaScript. Без знания что выбрать я начал пробовать все пакеты в том порядке, в каком мне их выдал Google. Оказалось, что не все пакеты используют последнюю версию компилятора (последняя версия была 1.5.0), и из-за этого код который компилировался на сайте TypeScript (ссылка) не компилировался плагином для gulp. Методом проб и ошибок я остановился на пакете gulp-tsc, который поддерживает все версии компилятора и работает «на ура».

Компиляция


Каждый интерфейс, сигнатура каждого экспортированного метода: все это должно быть известно компилятору, иначе он откажется компилировать код. Я работал с AMD модулями (об этом чуть позже), и при импорте одних модулей в другие возникала проблема — компилятор совершенно ничего не знал об существовании других модулей.

Для этих целей существуют .d.ts файлы — файлы, в которых нужно определить что конкретно экспортирует тот или иной модуль, какие глобальные переменные и функции определены.

На первый взгляд — все просто. На деле здесь обнаружился подводный камень (смотрим код ниже).

Создадим файл foo.ts в котором определим модуль foo:

/// <amd-module name="foo" />

export = {
	bar: () => 'baz'
}


Создадим файл bar.ts в котором определим модуль bar, который импортирует модуль foo:

/// <amd-module name="bar" />

import foo = require('foo'); // Ошибка: "Cannot find external module 'foo'."

export = {
	foo: foo
}


Мы получили ошибку «Не удается найти внешний модуль foo.». Почему так происходит? Так происходит потому что этот модуль мы нигде не определили и компилятор про него не знает.

Создадим файл foo.d.ts, в котором расскажем компилятору, что есть такой модуль foo, и он экспортирует один метод bar:

declare module foo {
	export function bar(): string
}


Мы добавили определение модуля и теперь все похоже на правду, все должно заработать, не правда ли? Неправда, потому что ровным счетом ничего от этого не изменилось — компилятор по-прежнему не может найти модуль foo. Вопрос — почему?

Решение оказалось неожиданным — название модуля было определено не в кавычках.

Работающий код файла foo.d.ts:

declare module 'foo' {
	export function bar(): string
}


Едем дальше...

AMD


Конечно, если TypeScript дает возможность создавать AMD-модули и можно красиво импортировать зависимости, почему бы этим не воспользоваться хотя бы для теста?

Я попробовал — в TypeScript по-определению нельзя генерировать модули с именами. TypeScript дает возможность генерировать модули без имен, не более. Мне показалось, что это было бы странно, и оказалось, что это можно обойти.

Пример модуля, который будет скомпилирован в модуль с именем:

/// <amd-module name="foo" />

export = {
	bar: () => 'baz'
}


Что еще интересного?


Большая часть времени, проведенного за кодированием на TypeScript, не доставила проблем, но довольно часто возникают тонкие моменты, решение которых довольно трудно найти. Одна из таких проблем, которую я не смог решить на текущий момент — как описать объект, свойства которого будут динамически определяться (названия свойств неизвестны), но каждый из них должен содержать объект, который имеет строго определенный интерфейс?

Пример неработающего кода:

interface IBar {
	baz: string
}

var foo: {
	[property: string]: IBar
}

foo = {};

foo.foobar = {
	baz: 'Hi there!'
}

Пример работающего кода:

interface IBar {
	baz: string
}

var foo: {
	[property: string]: IBar
}

foo = {
	foobar: {
		baz: 'Hi there!'
	}	
};

Это, конечно, не все проблемы, которые возникли, но каждая из них носит именно такой характер, как в примере выше.

Небольшой список других проблем:

  • Нельзя красиво определить сигнатуру конструктора;
  • Чтобы сказать TypeScript что мы ожидаем в методе получить конструктор в качестве параметра, а не экземпляр класса, нужно написать function foo (bar: typeof Baz) {… new Baz(...)… } а не просто function foo (bar: Baz) {… new Baz(...)… }.

Небольшой итог


Для себя я отметил TypeScript как отличный инструмент, который позволит исключить некоторые риски (в основном, исключение возможности ошибки, и все проблемы из этого вытекающие), позволяет жестко описать взаимодействие между элементами приложения, предлагает базовые инструменты для работы с модулями/классами и компилируется не в бороду из кода, а в достаточно читаемый код.

P.S Очень вероятно, что некоторые из проблем возникли лишь потому, что я еще не умею «готовить» TypeScript и поэтому некоторая информация может быть не объективно точной, но даже в этом случае я надеюсь, что эта информация окажется кому-нибудь полезной.

P.P.S Буду рад комментариям от знатоков TypeScript.
Tags:
Hubs:
Total votes 39: ↑33 and ↓6 +27
Views 147K
Comments Comments 42