Горячий от прессы, TypeScript был объявлен ранее на этой неделе и привел к взрыву комментариев через Twitter и различные блоги. Язык претендует на то, чтобы быть адаптированным для разработки приложений с использованием JavaScript, предоставляя типизированный надмножество. Он компилируется в JavaScript, поэтому он будет работать в любом браузере. Обещание состоит в том, чтобы упростить организацию и структурирование кода, а также избавиться от тех досадных ошибок, которые заставляют пользователей искать сотни строк кода в поисках ошибочной запятой или проблемы с чувствительностью к регистру.
По иронии судьбы я говорил с посетителем нашей конференции Devscovery о будущем JavaScript прямо перед объявлением TypeScript. Очевидно, что JavaScript здесь, чтобы остаться, и он не станет лучше. Он может получить лучшие детали, но обратная совместимость заставит плохие части оставаться в течение долгого времени. Что еще хуже, нет ничего, что убедило бы меня, что производители браузеров будут хорошо играть со спецификациями, когда у нас есть стандарт на протяжении почти 20 лет, и он все еще по-разному реализован в разных браузерах. Я пришел к выводу о том, что до того, как я несколько лет был испорчен простотой написания надежных бизнес-приложений Silverlight, я пришел к выводу, что, если нам приходится иметь дело с этим, нам нужно решать его с трех сторон:
- Образование: JavaScript — это динамический язык, где функции, а не классы, правила. Разработчики должны прекратить пытаться играть в объектно-ориентированного разработчика на клиентском пространстве и научиться играть по правилам клиента.
- Умеренность : сказав, что есть функции, которые просто делают слишком легким выстрелить себе в ногу. Несмотря на то, что есть подходящие решения для решения этой проблемы, такие как запуск JSHint / JSLint, я лично приветствую подход, позволяющий выявлять проблемы во время компиляции (или, что еще лучше, разработки) и сохранять нас всех честными. Ошибки легче исправить, чем ближе к источнику и чем раньше вы находитесь в процессе. Одна из вещей, которые я упомянул, заключалась в том, что должно быть реализовано что-то вроде CoffeeScript , которое помогает защитить нас от плохих частей при разработке, но все же генерирует в этот дружественный обратно-совместимый JavaScript-код, который любят все браузеры.
- Лучше клей . Да, я сказал это. Разработчики Silverlight знают, о чем я говорю * кашляю * RIA Services * кашляю …… теперь, когда у нас есть очень стандартные способы представления API, должны быть более простые способы приклеить клиента к серверу, чтобы разработчики работали над «чем отличается И не тратить 75% своего времени на создание методов контроллера и написание AJAX-вызовов для клиента.
Я считаю, что TypeScript — это инструмент, который сохраняет первый элемент (он поддерживает JavaScript и не пытается скрыть динамическую и гибкую природу) и обращается ко второму элементу (он позволяет нам гораздо яснее заявить о своем намерении и сразу получить обратную связь) и, вероятно, ничего не делает для последнего элемента.
Вместо того, чтобы обсуждать, нужен ли нам другой язык, или он хороший или плохой (и, эй, я люблю C #, и я любил TurboPascal, так что тот факт, что он был разработан Андерсом Хейлсбергом , не помешает), я бы лучше занялся используя его, я могу начать понимать, как это помогает или причиняет боль. Завтра я буду в течение трех часов выступать на эту конкретную тему (как использовать библиотеки в разработке JavaScript для помощи в масштабировании для больших проектов и для больших групп), поэтому я решил взять пример, который я создал с Backbone.JS, чтобы увидеть, что это будет принять в порт. Вот так!
Первым шагом была просто реализация подключаемого модуля VS 2012. Я уверен, что с треском провалился. Я получаю подсветку синтаксиса, но нет шаблона для добавления нового файла TypeScript, нет автоматической компиляции и нет эффективного IntelliSense. Я уверен, что это ошибка оператора, так что следите за обновлениями. Вы можете получить все, что вам нужно (или я должен сказать «все, что доступно») по ссылке загрузки TypeScript . Затем я добавил файл JavaScript и переименовал его, чтобы он имел расширение .ts, и добавил сценарий после сборки для его компиляции:
tsc $(ProjectDir)/Scripts/app.ts
Затем я взял спецификацию языка и пошел на работу. Во-первых, я обнаружил, что мне нужно объявить некоторые переменные для учета внешних модулей:
declare var $: any; declare var _: any;
Это было просто! Однако использование Backbone было немного сложнее. Я обманул, взглянув на существующий образец TodoMVC с сайта TypeScript. Чтобы Backbone хорошо играл с TypeScript, я должен определить модуль и экспортировать классы, которые описывают API. Это не должен быть полный API, но он может сосредоточиться на частях, которые я буду использовать. Вот пример экспорта объекта Backbone Model в виде класса:
declare module Backbone { export class Model { constructor (attr? , opts? ); get(name: string): any; set(name: string, val: any): void; set(obj: any): void; save(attr? , opts? ): void; destroy(): void; bind(ev: string, f: Function, ctx?: any): void; toJSON(): any; } }
Обратите внимание, что у меня есть дополнительные параметры в конструкторе. Я могу объявить намерение, указав тип для именованных параметров и тип возвращаемого значения функции. Довольно круто. Мне также нравится использовать Backbone.Events, а TypeScript позволяет мне определять интерфейсы. Вот интерфейс для событий, определяющих подпись для «вкл» и «выкл» (зарегистрируйтесь и отмените регистрацию):
interface HasEvents { on: (event: string, callback: (parms?: any, moreParms?: any) => any) => any; off: (event: string) => any; }
Самый простой способ прочитать это, на мой взгляд, справа налево. Функция, которая возвращает любой тип, принимает параметры, ну, в случае первого, функции, которая имеет два необязательных параметра и возвращает любой тип, а первый параметр является строкой. Я также сделал интерфейс для простой версии контакта, чтобы было понятно, когда я ожидаю такой пример:
interface ContactInterface { id?: number; firstName: string; lastName: string; email: string; address: string; city: string; state: string; };
Вы также можете объявить статические свойства для классов, поэтому я создал шаблонный класс просто для хранения скомпилированных шаблонов, которые я использую:
class Templates { static contact: (json: any) => string; static editContact: (json: any) => string; static contacts: (json: any) => string; };
Обратите внимание, что скомпилированный шаблон принимает любой тип объекта и возвращает строку. Наконец, я создал глобальный объект для размещения сквозных событий вместо использования библиотеки pub / sub, такой как Postal . Обратите внимание на реализацию интерфейса HasEvents .
class AppEvents implements HasEvents { on: (event: string, callback: (model: Contact, error: any) => any) => any; off: (event: string) => any; trigger: (event: string, data: any) => any; };
Наконец, вы видите простой JavaScript — я создаю объект и расширяю его, чтобы иметь события:
var appEvents = new AppEvents(); _.extend(appEvents, Backbone.Events);
Итак, теперь давайте рассмотрим, как мы это делали «по-старому» против «по-новому» Вот старый способ, которым я определил свою модель контакта:
App.Models.Contact = Backbone.Model.extend({ initialize: function () { console.log("Contact init."); }, defaults: function () { return { id: null, firstName: '', lastName: '', email: '', address: '', city: '', state: '' }; }, validate: function (attrs) { if (_.isEmpty(attrs.firstName)) { return "First name is required."; } if (_.isEmpty(attrs.lastName)) { return "Last name is required."; } if (_.isEmpty(attrs.email)) { return "Email is required."; } if (attrs.email.indexOf("@") < 1 || attrs.email.indexOf(".") < 1) { return "The email address appears to be invalid."; } return ""; } });
По сути, я что-то записываю на консоль, предоставляю шаблон для экземпляра контакта и объявляю мои проверки. Обратите внимание, что нет способа гарантировать то, что я возвращаю из функции (я мог бы так же легко отправить обратно {foo: “bar”} ), и что атрибуты для проверки могут быть чем угодно. С TypeScript я могу быть более конкретным и даже генерировать ошибки времени разработки (и времени компиляции), если я не делаю то, что ожидаю:
class Contact extends Backbone.Model implements HasEvents { on: (event: string, callback: (model: Contact, error: any) => any) => any; off: (event: string) => any; isValid: () => bool; initialize() { console.log("Contact init."); }; defaults() : ContactInterface { return { id: null, firstName: '', lastName: '', email: '', address: '', city: '', state: '' }; }; validate(attrs: ContactInterface) { if (_.isEmpty(attrs.firstName)) { return "First name is required."; } if (_.isEmpty(attrs.lastName)) { return "Last name is required."; } if (_.isEmpty(attrs.email)) { return "Email is required."; } if (attrs.email.indexOf("@") < 1 || attrs.email.indexOf(".") < 1) { return "The email address appears to be invalid."; } return ""; } };
Обратите внимание, что я реализую события более конкретным способом, предоставляя подробную подпись для обратного вызова. Функции просто объявляются аналогично тому, как они определены в классах C #, и мне не нужно беспокоиться о представлении имени свойства. Наконец, я могу ввести возвращаемое значение и параметры для различных функций. Довольно круто, нет? Я все еще использую JavaScript, и объект все еще может быть динамическим, но я могу по крайней мере ограничить его конкретным интерфейсом, который я собираюсь использовать в своем методе.
Вот старое определение для коллекции, передавая параметры для ее настройки, чтобы они указывали на REST API на моем сервере:
App.Collections.Contacts = Backbone.Collection.extend({ model: App.Models.Contact, url: "/api/Contacts", initialize: function () { console.log("Contacts init."); } });
Вот новое определение, которое происходит от базового класса и использует вместо него конструктор. Я также ясно даю понять, что использую события в коллекции, чего не видно в оригинальном JavaScript.
class Contacts extends Backbone.Collection implements HasEvents { on: (event: string, callback: (parms?: any, moreParms?: any) => any) => any; off: (event: string) => any; url: string; model: Contact; initialize() { console.log("Contacts init."); } constructor(options?: any) { this.url = "/api/Contacts"; super(options); }; };
Еще одна интересная особенность — я могу объявлять типы, а затем приводить вызовы, которые не видны типу. Например, я не сообщил TypeScript, что jQuery может вернуть элемент DOM, но в моем объявлении для представлений я использую тип HTMLElement. Чтобы согласовать это, я могу просто привести результат так:
this.el = <HTMLElement>$("#contact");
Я знаю, что это похоже на HTML-тег, но это потому, что это не истинное приведение, а всего лишь подсказка, что это ожидаемый тип возврата. После еще нескольких моделей и представлений я определяю общий объект приложения, который раскручивает начальную коллекцию в конструкторе. Обратите внимание, как я также устанавливаю свойства статического шаблона — очень простой синтаксис. Создание объекта запускает приложение:
class Application { public static editContactView: EditContactView; constructor() { Templates.contact = _.template($("#contactTemplate").html()); Templates.editContact = _.template($("#editContactTemplate").html()); Templates.contacts = _.template($("#contactsTemplate").html()); var contacts = new Contacts(); contacts.fetch(); var contactsView = new ContactList({ collection: contacts }); contactsView.render(); }; }; // host container var App = new Application();
Приложение демонстрирует CRUD со списком, который заполняет форму с проверкой, где пользователь может редактировать, удалять и добавлять новые контакты.
Процесс сборки компилирует TypeScript в файл JavaScript, на который фактически ссылаются из представления MVC. Вы не увидите интерфейсы или определения, потому что они помогают навязывать вещи во время разработки и компиляции. Как выглядит результат? Вот скомпилированный объект события всего приложения, который я создал:
var AppEvents = (function () { function AppEvents() { } return AppEvents; })(); ; ; var appEvents = new AppEvents(); _.extend(appEvents, Backbone.Events);
Вот определение модели контакта — обратите внимание, как передается базовая модель для расширения:
var Contact = (function (_super) { __extends(Contact, _super); function Contact() { _super.apply(this, arguments); } Contact.prototype.initialize = function () { console.log("Contact init."); }; Contact.prototype.defaults = function () { return { id: null, firstName: '', lastName: '', email: '', address: '', city: '', state: '' }; }; Contact.prototype.validate = function (attrs) { if(_.isEmpty(attrs.firstName)) { return "First name is required."; } if(_.isEmpty(attrs.lastName)) { return "Last name is required."; } if(_.isEmpty(attrs.email)) { return "Email is required."; } if(attrs.email.indexOf("@") < 1 || attrs.email.indexOf(".") < 1) { return "The email address appears to be invalid."; } return ""; }; return Contact; })(Backbone.Model);
Джереми, где полная загрузка? Извините, сейчас проект зарезервирован для посетителей Devscovery — у меня может быть полный проект, который будет доступен после этой недели, но сейчас вы можете посмотреть полный исходный код TypeScript (это все клиентское приложение), нажав здесь ,
Благодаря! Что я думаю? Пока мои мысли очень позитивны. Я считаю, что это полезный инструмент, который позволяет командам писать код, к которому они привыкли, с возможностью добавлять меры безопасности и типы, где это необходимо, чтобы избежать ошибок и облегчить организацию, чтение и понимание кода. Мне придется использовать его еще немного, но этот короткий проект преобразования приложения заставил меня поднять оба больших пальца вверх.