Когда я начал писать эту статью, исходным рабочим названием было «Angular не испортил JavaScript, TypeScript сделал». Тем не менее, это не совсем вывод этой статьи (а скорее «ссылочная приманка» в конце концов). Я люблю вас всех слишком сильно, чтобы сделать это с вами.
Но позвольте мне начать с чего-то немного зажигательного, объясняющего, почему я изначально думал, что TypeScript фактически разрушил JavaScript.
JavaScript, Добро пожаловать в ад
Я помню, когда TypeScript был впервые выпущен, я смотрел видео с Андерсом Хейлсбергом , который демонстрировал некоторые типы, классы, интерфейсы и другие подобные статические языковые эффекты, которые все переходили на простой JavaScript.
И я подумал: «Нет. НЕТ НЕТ НЕТ НОО НОООООООО! »
Я ненавижу типы
Я всегда любил JavaScript. С того момента, как я впервые увидел это. Первым языком, который я выучил, был ActionScript, а затем и C #. И я ненавидел C #. Ну, не синтаксис C #, но я определенно хотел, чтобы компилятор гнил в аду.
Мне показалось, что компилятор был излишне тупым.
Рассмотрим следующее …
var i = 1;
var x = null;
Console.WriteLine(i == x);
// ERROR: Cannot assign <null> to an implicitly-typed variable
В самом деле? В языке есть только одно нулевое значение, и вы не можете с этим справиться?
«Ну, нет, Берк. Потому что вы должны знать, какой тип установить на нуль, если он не будет нулевым позже ».
Я не уверен, почему мы не можем просто иметь дело с этим, когда мы действительно заинтересованы в типе, но хорошо, я признаю это.
var i = 1;
int x = null;
Console.WriteLine(i == x);
// ERROR: Cannot convert null to 'int' because it is a non-nullable value type
Вы не можете установить значение целого числа в ноль, не сказав сначала, что оно может быть обнуляемым. На данный момент от меня уже потребовалось слишком много ручного труда. Я пытаюсь решить реальные проблемы здесь, и у меня нет времени, чтобы держать компьютер за руку с основными понятиями, такими как числа. Я уверен, что есть веская академическая причина для обнуляемых типов, но мне все равно .
Я потратил так много времени, как разработчик C #, просто сражаясь с типами. Это все. И это даже не учитывая такие вещи, как рефлексия, тривиальное дело в JavaScript, которое приравнивается к абсолютному цирку в C #.
Моя фундаментальная проблема с типизированными системами заключается в том, что они заставляют вас заранее знать точный формат каждой входящей структуры данных, с которой вы собираетесь работать, что зачастую невозможно.
Рассмотрим сценарий, в котором у вас есть база данных, которая принимает пустой тип в целочисленном поле. Допустим, вы работаете с модулем, который вы не можете изменить, используя целочисленные типы, не имеющие значения NULL. Как вы справляетесь с нулевым сценарием? Обычно это так:
db.propertyToUpdate = sealedModule.fieldINeed == -1 ?
null : sealedModule.fieldINeed;
На этом этапе, помимо ощущения полной неудачи в жизни, у вас также есть технический долг, поскольку вы вынуждены искажать свой код, чтобы угодить компилятору.
Так что если строгая типизация так ужасна, почему многим разработчикам это нравится? Одним словом — Повышенная производительность.
Увеличение производительности
Хорошо, это было два слова.
Я знаю, что только что объяснил, как типы заставляют разработчиков нести технические долги, а затем я сказал, что типизированные языки повышают производительность, и это нечто большее, чем просто противоречие, но это одна из тех вещей с обоюдоострым мечом.
Типы — это то, что делает IDE больше, чем просто текстовый редактор. Чаще всего мы работаем с кодом, который мы не писали, чаще всего с использованием фреймворка. У фреймворка есть API, о котором мы (по крайней мере изначально) ничего не знаем. Это означает, что для продуктивной работы с фреймворком вы должны не только знать, как кодировать, но и освоить чужой код. Кажется, нечестно, не так ли?
И изучение всей структуры не является маленькой задачей. Спустя 5 лет я все еще нахожусь в документации по jQuery, и это даже не сложная или большая поверхность API. Когда вы говорите о чем-то таком большом, как .NET Framework или Cocoa, просто нет способа узнать все это без многолетнего опыта.
Давайте возьмем просто строку в C #. Согласно официальной документации для .NET 4.5 , класс String имеет 8 конструкторов и 120 методов ! Это даже не включает методы расширения или операторы.
В отличие от этого, объект JavaScript String имеет 1 конструктор и 21 метод. Даже имея всего 21 метод, я все равно время от времени ищу их.
Именно здесь вступает в силу IDE и делает возможным использование таких масштабных сред, как .NET. Мне не нужно ссылаться на строковую документацию, мне просто нужно начать использовать строку. Когда я это сделаю, IDE сообщит мне, какие методы я могу использовать, и расскажет, что они делают, и я никогда не покину редактор и не потеряю контекст.
И вот тут-то мы и добрались до TypeScript.
До сих пор строго типизированные языки представляли собой пакет «все или ничего» TypeScript отличается от других. Это приносит лучшие части набора текста — особенно увеличенную производительность через интеграцию IDE — и оставляет нелепую часть кастинга как дополнительную. Я не полностью осознавал всю мощь этого до недавнего времени, когда занимался разработкой NativeScript .
Значение TypeScript
В последнее время я много работал над NativeScript, и NativeScript рассматривает TypeScript как гражданина первого класса — действительно BFF. Фактически, все кроссплатформенные модули в NativeScript (tns_modules) написаны на TypeScript. AppBuilder For Visual Studio предоставляет шаблон TypeScript для NativeScript, поэтому я решил попробовать и посмотреть, как это на самом деле использовать TypeScript в Visual Studio.
Если я пишу простое приложение на NativeScript, я начинаю с разметки для страницы (мобильный просмотр). При этом Visual Studio предлагает мне некоторые события, которые я могу использовать, такие как часто используемое loaded
событие в NativeScript. Это сделано потому, что NativeScript предоставляет определение схемы в Visual Studio для разметки. Но это Visual Studio, которая предоставляет редактор магии. Я также могу создать TextField без необходимости помнить, что такое разметка.
Рассмотрим следующий код со страницы NativeScript, который обрабатывает loaded
событие. Это может быть написано со стандартным JavaScript ES5, как это так …
var viewModule = require('ui/view');
exports.loaded = function(args) {
var page = args.object;
greeting = viewModule.getViewById(page, "txtGreeting");
greeting.text = "Hello Indeed";
}
Это CommonJS, который NativeScript также поддерживает как первоклассный гражданин. Проблема в том, что я часто возвращаюсь к документации по NativeScript, чтобы попытаться вспомнить, какие свойства у каких объектов и какие объекты передаются в какие события.
Вот что происходит в этом коде …
- Загруженное событие определено.
- Ссылка на текущую страницу (xml) получена.
- Ссылка на TextField получена.
- Текст TextField изменен на «Привет, действительно»
Проблема в том, что это всего лишь JavaScript, и мне нужно много знать о NativeScript здесь. Я должен знать о загруженном событии, я должен знать, какой объект получает событие, я должен знать, как получить ссылку на страницу. Я должен знать, что мне нужен модуль представления для того, чтобы получить эту ссылку, и я должен знать, каков метод в модуле представления и какие параметры ему нужны. Наконец, мне нужно знать, какой метод вызывать для экземпляра элемента управления, чтобы установить его текст.
Это очень много , чтобы идти в ногу с этим, и мы не особо много делаем. Кроме того, ни один из этого кода не проверяется во время разработки, поэтому любые нулевые ссылки на объекты или методы приведут к аварийному завершению работы приложения.
Теперь давайте перепишем это с TypeScript и посмотрим, как это упрощается.
Since TypeScript is a first-class citizen in NativeScript, we can re-write this in a .ts file
I’m just going to start under the assumption that I know that I need a loaded
event based on what I built in the XML.
export function loaded() {
//...
};
So far, this looks familiar. Now we just need to know what type of event the loaded
function is going to receive. According to the documentation, all events receive an observable. To use an observable, we need to import one.
import * as observable from 'data/observable';
export function loaded(args: observable.EventData) {
//...
};
If that import looks terrifying to you, then you are just like me. That’s actually ES6 syntax for modules. One of the neat things about using TypeScript, is that it implements quite a bit of ES6 where it can, and uses proprietary syntax where the spec is incomplete or missing.
In this case, we are saying, “Import everything from the observable file and put it all in an object called observable
.”
Now watch the magic unfold after we have gotten this far. I know that I need a page reference, so I check the args object.
I’m given two options – eventData
and object
. These are the only two properties that are coming in on the event. I can safely assume that object is what I need. Now I need to cast it to a page
. I need the Page module to do that, and TypeScript is aware of the type.
import * as page from 'ui/page';
And now I can cast the object to a Page type.
More ES6! I can use the let
and const
keywords in TypeScript. I could also just use var
and that would be fine too. Now that I have the page and TypeScript knows that it’s a page
object, I can use the getViewById
method to get a reference to the TextField.
That returns me a generic view
object, which is subclassed by all the UI components. That means I need to cast it to a TextField
type.
There’s a lot going on in that GIF. Notice that I make a mistake and try to set the text via a method. TypeScript catches this for me and points out that this is a property, not a method. Also notice that my import
of the TextField type looks a tad funkier than the other imports. That’s because ES6 modules allow you to import any property or method exposed by a module as a single import. This allows you to cherry-pick just want you want from a module.
My favorite thing about all of this is the fact that I know, without a shadow of a doubt, that when I run this, it’s going to work. If not, it’s not going to because I made some stupid mistake. The browser is forgiving. Native applications are not. Errors aren’t just shelled to the console. They result in nasty app crashes and in the case of mobile apps, the app just crashes and doesn’t tell the user anything at all. I’ve always thought this was a horrible user experience.
The Best Part
Now the best part of all this? I can still do this…
var x = 1;
var i = null;
console.log(x === i); // FALSE
HAHA! I win! All of the benefits of the IDE and none of the soul crushing insanity of strongly typed systems. As they say in the sales game, it’s a “win-win.”
Should You Use TypeScript?
It depends.
I think that due to the browsers’ incredibly forgiving nature for your crappy code, you are just fine with plain JavaScript. I’m not convinced that the browser environment demands such stringent expectations of your code. I think you will iterate faster without it to be honest.
However…JavaScript is being written in more and more places, including being used to create native mobile application. NativeScript is just one example. In these cases, I imagine that you would want the power of TypeScript.
I also think that TypeScript encourages you to learn ES6. That’s imperative since ES6 is coming at us at a screaming pace. It’s a pretty big change, and you can’t avoid it forever. TypeScript is a great way to ease into some of the essential ES6 concepts, such as modules.
In any case, TypeScript has made a fan out of me. I know that I prefer to use it in my NativeScript projects, and I’m looking forward to doing more with it, especially as Angular 2 looms on the horizon.
TypeScript support is available today in the AppBuilder Plugin For Visual Studio.