Горячий от прессы, 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 (это все клиентское приложение), нажав здесь ,
Благодаря! Что я думаю? Пока мои мысли очень позитивны. Я считаю, что это полезный инструмент, который позволяет командам писать код, к которому они привыкли, с возможностью добавлять меры безопасности и типы, где это необходимо, чтобы избежать ошибок и облегчить организацию, чтение и понимание кода. Мне придется использовать его еще немного, но этот короткий проект преобразования приложения заставил меня поднять оба больших пальца вверх.
