JavaScript

TypeScript: Deixando os desenvolvedores do .NET confortáveis com o JavaScript

Shayne Boyer

 

Sem dúvida, você tem um gigantesco investimento no Microsoft .NET Framework, que é realmente uma plataforma essencial com uma abundância de ferramentas disponíveis. Se você combinar seu conhecimento em C# ou Visual Basic .NET com XAML, o mercado para suas habilidades atuais poderá parecer quase ilimitado. No entanto, hoje em dia, você precisa considerar uma linguagem que já esteja bem estabelecida há algum tempo e que tenha, nos últimos anos, realmente se tornado a plataforma de aplicativos favorita. Estamos falando sobre o JavaScript, é claro. O crescimento e a capacidade dos aplicativos JavaScript são imensos. Node.js, uma plataforma inteira para desenvolvimento de aplicativos JavaScript escalonáveis, tornou-se consideravelmente popular, além de ser implantável até mesmo no Windows Azure. Além disso, o JavaScript pode ser usado com HTML5 para desenvolvimento de jogos, aplicativos móveis e, até mesmo, aplicativos da Windows Store.

Como um desenvolvedor do .NET, você não pode ignorar os recursos do JavaScript, nem sua proliferação no mercado. Quando faço essa afirmação aos colegas, muitas vezes, ouço reclamações sobre como é difícil trabalhar com o JavaScript, que não há rigidez de tipos, nem estruturas de classe. Combato tais argumentos respondendo que o JavaScript é uma linguagem funcional e que há padrões para se fazer o que quiser.

Aí entra o TypeScript. O TypeScript não é uma nova linguagem. É um superconjunto do JavaScript - um superconjunto tipado e poderoso, que significa que todo o JavaScript é um TypeScript válido, e o que é produzido pelo compilador é JavaScript. O TypeScript é um projeto de software livre e todas as informações relacionadas ao projeto podem ser encontradas em typescriptlang.org. No momento em que escrevo este artigo, o TypeScript estava na versão de demonstração 0.8.1.

Neste artigo, abordarei os conceitos básicos do TypeScript na forma de classes, módulos e tipos, para mostrar como um desenvolvedor do .NET pode se sentir mais confortável com um projeto JavaScript.

Classes

Se você trabalha com linguagens como C# ou Visual Basic .NET, as classes são um conceito conhecido. No JavaScript, as classes e a herança são executadas por meio de padrões, como fechamentos e protótipos. O TypeScript introduz a sintaxe do tipo clássico que você costumava usar e o compilador produz o JavaScript que atinge o objetivo. Tome o seguinte trecho do JavaScript:

 

var car; car.wheels = 4; car.doors = 4;

Parece simples e direto. No entanto, os desenvolvedores do .NET têm se mostrado indecisos quanto a aceitar o JavaScript devido à sua abordagem vulnerável para definição de objetos. O objeto car pode ter propriedades adicionais incluídas posteriormente, sem imposição e sem saber qual tipo de dados cada uma representa e, desse modo, acaba gerando exceções durante o tempo de execução. Como a definição do modelo de classe do TypeScript muda isso e como herdamos e estendemos o objeto car? Considere o exemplo da Figura 1.

Figura 1 Objetos no TypeScript e JavaScript

TypeScript JavaScript
class Auto{  wheels;  doors;}var car = new Auto();car.wheels = 2;car.doors = 4; var Auto = (function () {  function Auto() { }  return Auto;})();var car = new Auto();car.wheels = 2;car.doors = 4;

À esquerda está um objeto de classe habilmente definido chamado car, com as propriedades wheels e doors. À direita, o JavaScript produzido pelo compilador TypeScript é quase igual. A única diferença é a variável Auto.

No editor do TypeScript, não é possível incluir uma propriedade adicional sem receber um aviso. Você não pode simplesmente começar usando uma instrução como car.trunk = 1. O compilador reclamará, "No trunk property exists on Auto", que é uma dádiva para qualquer pessoa que já teve que identificar essa pegadinha devido à flexibilidade do JavaScript, ou dependendo do seu ponto de vista, da "preguiça" do JavaScript.

Os construtores, embora disponíveis no JavaScript, foram aprimorados com as ferramentas do TypeScript novamente pela aplicação da criação do objeto durante o tempo de compilação e ao não permitir que o objeto seja criado sem passar os tipos e elementos apropriados na chamada.

Além de poder adicionar o construtor à classe, você pode tornar os parâmetros opcionais, definir um valor padrão ou resumir a declaração de propriedade. Vamos dar uma olhada nos três exemplos que mostram quão poderoso o TypeScript pode ser.

A Figura 2 mostra o primeiro exemplo, um construtor simples no qual a classe é inicializada pela passagem dos parâmetros wheels e doors (representados aqui por w e d). O JavaScript produzido (à direita) é quase igual, mas como a dinâmica e as necessidades do seus aplicativo se expandem, esse nem sempre será o caso.

Figura 2 Um construtor simples

TypeScript JavaScript
class Auto{  wheels;  doors;  constructor(w, d){    this.wheels = w;    this.doors = d;  }}var car = new Auto(2, 4); var Auto = (function () {  function Auto(w, d) {    this.wheels = w;    this.doors = d;  }  return Auto;})();var car = new Auto(2, 4);

 

Na Figura 3, modifiquei o código na Figura 2, padronizando o parâmetro wheels (w) para 4 e tornando o parâmetro doors (d) opcional inserindo um ponto de interrogação à direita dele. Observe que, como no exemplo anterior, o padrão de configuração da propriedade de instância para o argumento é uma prática comum que usa a palavra-chave "this".

Figura 3 Um construtor simples, modificado

TypeScript JavaScript
class Auto{  wheels;  doors;  constructor(w = 4, d?){    this.wheels = w;    this.doors = d;  }}var car = new Auto(); var Auto = (function () {  function Auto(w, d) {    this.wheels = w;    this.doors = d;  }  return Auto;})();var car = new Auto(4, 2);

Veja um recurso que adoraria ver nas linguagens do .NET: poder simplesmente adicionar a palavra-chave pública antes do nome do parâmetro no construtor para declarar a propriedade na classe. A palavra-chave privada está disponível e faz a mesma declaração automática, mas oculta a propriedade da classe.

Os valores padrão, os parâmetros opcionais e as anotações de tipo são estendidos com o recurso de declaração automática de propriedade do TypeScript, o que o torna um belo atalho - e torna você mais produtivo. Compare o script na Figura 4 e você poderá ver as diferenças na complexidade que começam a aparecer.

Figura 4 O recurso de declaração automática

TypeScript JavaScript
class Auto{  constructor(public wheels = 4,    public doors?){  }}var car = new Auto();car.doors = 2; var Auto = (function () {  function Auto(wheels, doors) {    if (typeof wheels ===      "undefined") {      wheels = 4; }    this.wheels = wheels;    this.doors = doors;  }  return Auto;})();var car = new Auto();car.doors = 2;

 

As classes no TypeScript também fornecem herança. Ainda com o exemplo Auto, você pode criar uma classe Motorcycle que estenda a classe inicial. Na Figura 5, também adiciono as funções drive e stop à classe base. A adição da classe Motorcycle, herdada de Auto e que define as propriedades adequadas para doors e wheels, é realizada com algumas linhas de código no TypeScript.

Figura 5 Adicionando a classe Motorcycle

class Auto{   constructor(public mph = 0,     public wheels = 4,     public doors?){   }   drive(speed){     this.mph += speed;   }   stop(){     this.mph = 0;   } } class Motorcycle extends Auto {   doors = 0;   wheels = 2; } var bike = new Motorcycle();

Algo importante a ser mencionado aqui é que, no início do JavaScript produzido pelo compilador, você verá uma pequena função chamada " ___extends", conforme mostrado na Figura 6, que é o único código já injetado no JavaScript resultante. Essa é uma classe auxiliar que ajuda na funcionalidade de herança. Apenas para rápida observação, essa função auxiliar tem a mesma assinatura, independentemente da origem, de modo que se estiver organizando seu JavaScript em vários arquivos e usar um utilitário como SquishIt ou Web Essentials para combinar seus scripts, você poderá receber um erro, dependendo de como o utilitário retifica funções duplicadas.

Figura 6 O JavaScript produzido pelo compilador

var __extends = this.__extends || function (d, b) {   function __() { this.constructor = d; }   __.prototype = b.prototype;   d.prototype = new __(); } var Auto = (function () {   function Auto(mph, wheels, doors) {     if (typeof mph === "undefined") { mph = 0; }     if (typeof wheels === "undefined") { wheels = 4; }     this.mph = mph;     this.wheels = wheels;     this.doors = doors;   }   Auto.prototype.drive = function (speed) {     this.mph += speed;   };   Auto.prototype.stop = function () {     this.mph = 0;   };   return Auto; })(); var Motorcycle = (function (_super) {   __extends(Motorcycle, _super);   function Motorcycle() {     _super.apply(this, arguments);     this.doors = 0;     this.wheels = 2;   }   return Motorcycle; })(Auto); var bike = new Motorcycle();

Módulos

Os módulos no TypeScript equivalem aos namespaces no .NET Framework. Eles são uma excelente maneira de organizar seu código e encapsular regras e processos de negócios. o que não seria possível sem essa funcionalidade (o JavaScript não tem uma maneira interna de fornecer essa função). O padrão de módulo, ou namespace dinâmico, como em JQuery, é o padrão mais comum para namespaces no JavaScript. Os módulos do TypeScript simplificam a sintaxe e produzem o mesmo efeito. No exemplo Auto, você pode encapsular o código em um módulo e expor somente a classe Motorcycle, conforme mostrado na Figura 7.

O módulo Example encapsula a classe base e a classe Motorcycle é exposta ao prefixá-la com a palavra-chave export. Isso permite que uma instância de Motorcycle seja criada e todos os seus métodos usados, mas a classe base Auto é ocultada.

Figura 7 Encapsulando a classe Auto em um módulo

module Example {   class Auto{     constructor(public mph : number = 0,       public wheels = 4,       public doors?){       }       drive(speed){       this.mph += speed;       }       stop(){       this.mph = 0;       }   }   export class Motorcycle extends Auto   {     doors = 0;     wheels = 2;   } } var bike = new Example.Motorcycle();

Outro benefício dos módulos é poder mesclá-los. Se você criar outro módulo também denominado Example, o TypeScript suporá que o código no primeiro módulo e o código no novo módulo possam ser acessados por meio de instruções Example, assim como nos namespaces.

Os módulos facilitam a capacidade de manutenção e a organização do seu código. Com eles, sustentar aplicativos de larga escala se torna menos uma sobrecarga para as equipes de desenvolvimento.

Tipos

A falta de segurança de tipos é uma das principais reclamações que ouvi dos desenvolvedores que não mergulham no mundo do JavaScript todos os dias. Porém, a segurança de tipos está disponível no TypeScript (por isso é chamado de TypeScript) e vai além de apenas declarar uma variável como uma cadeia de caracteres ou um booliano.

No JavaScript, a prática de atribuir foo a x e, posteriormente, no código atribuir 11 a x é perfeitamente aceitável, mas isso poderá enlouquecer você quando estiver tentando entender por que você está recebendo o NaN constante durante o tempo de execução.

O recurso de segurança de tipo é uma das maiores vantagens do TypeScript e há quatro tipos inerentes: cadeia de caracteres, número, booliano e qualquer. A Figura 8 mostra a sintaxe para declarar o tipo das variáveis e o IntelliSense que o compilador fornece assim que toma conhecimento das ações que você pode executar com base no tipo.


Figura 8 Um exemplo do IntelliSense do TypeScript

Além de permitir a definição do tipo de uma variável ou função, o TypeScript tem a capacidade de inferir tipos. Você pode criar uma função que simplesmente retorne uma cadeia de caracteres. Sabendo disso, o compilador e as ferramentas fornecem inferência de tipo e mostram automaticamente as operações que podem ser executadas no retorno, como pode ser visto na Figura 9.


Figura 9 Um exemplo de inferência de tipo

O benefício aqui é que você pode ver que o retorno é uma cadeia de caracteres sem precisar de adivinhação. A inferência de tipo é uma importante ajuda quando se trata de trabalhar com outras bibliotecas às quais os desenvolvedores fazem referência no respectivo código, como JQuery ou até mesmo o DOM (Document Object Model).

Outra maneira de aproveitar o sistema de tipo é por meio de anotações. Olhando para trás, a classe Auto original foi declarada com apenas wheels e doors. Agora, por meio das anotações, podemos garantir que os tipos apropriados serão definidos ao criar a instância de Auto em car:

 

class Auto{   wheels : number;   doors : number; } var car = new Auto(); car.doors = 4; car.wheels = 4;

No entanto, no JavaScript que é produzido, as anotações são compiladas em outro lugar, de modo que não há FAT e nem dependências adicionais com as quais se preocupar. O benefício, mais uma vez, é uma definição de tipo forte e, além disso, a eliminação de erros simples que geralmente são encontrados durante o tempo de execução.

As interfaces fornecem outro exemplo da segurança de tipo oferecida no TypeScript. As interfaces permitem definir a forma de um objeto. Na Figura 10, um novo método denominado travel foi adicionado à classe Auto e ele aceita um parâmetro com um tipo de Trip.

Figura 10 A interface Trip

interface Trip{   destination : string;   when: any; } class Auto{   wheels : number;   doors : number;   travel(t : Trip) {   //..   } } var car = new Auto(); car.doors = 4; car.wheels = 4; car.travel({destination: "anywhere", when: "now"});

Se você tentar chamar o método travel sem a estrutura correta, o compilador de tempo de design vai gerar um erro. Em comparação, se você inserir esse código no JavaScript, por exemplo, em um arquivo .js, muito provavelmente você não obterá um erro como esse até que tenha executado o aplicativo.

Na Figura 11, você pode ver que aproveitar as anotações de tipo auxilia significativamente o desenvolvedor inicial, assim como o desenvolvedor subsequente que precisa manter a origem.


Figura 11 Anotações auxiliam na manutenção do seu código

Bibliotecas e códigos existentes

E quanto seu código JavaScript existente? E se você adorasse criar no Node.js ou usar bibliotecas como a toastr, Knockout ou JQuery? O TypeScript tem arquivos de declaração para ajudar. Em primeiro lugar, lembre-se de que todo JavaScript é um TypeScript válido. Desse modo, se você tiver algo doméstico, será possível copiar esse código diretamente no designer e o compilador produzirá o JavaScript, um por um. A melhor opção é criar seu próprio arquivo de declaração.

Para obter as principais bibliotecas e estruturas, um cavalheiro chamado Boris Yankov (twitter.com/borisyankov no Twitter) criou um bom repositório no GitHub (github.com/borisyankov/DefinitelyTyped) que tem vários arquivos de declaração para algumas das bibliotecas JavaScript mais populares. Isso é exatamente o que a equipe do TypeScript esperava que acontecesse. A propósito, o arquivo de declaração de Node.js foi criado pela equipe do TypeScript e está disponível como parte do código-fonte.

Criando um arquivo de declaração

Se não for possível localizar o arquivo de declaração para sua biblioteca ou se estiver trabalhando com seu próprio código, você precisará criar um arquivo de declaração. Comece copiando o JavaScript no lado do TypeScript e adicionando as definições de tipo e, em seguida, use a ferramenta de linha de comando para gerar o arquivo de definição (*.d.ts) para referência.

A Figura 12 mostra um script simples para calcular a média das notas no JavaScript. Copiei o script no lado esquerdo do editor e adicionei as anotações para os tipos, e salvarei o arquivo com a extensão .ts.

Figura 12 Criando um arquivo de declaração

TypeScript JavaScript
function gradeAverage(grades : string[]) {  var total = 0;  var g = null;  var i = -1;  for(i = 0; i < grades.length; i++) {      g = grades[i];      total += getPointEquiv(grades[i]);  }  var avg = total / grades.length;  return getLetterGrade(Math.round(avg));}function getPointEquiv(grade : string) {  var res;  switch(grade) {    case "A": {      res = 4;      break;    }    case "B": {      res = 3;      break;    }    case "C": {      res = 2;      break;    }    case "D": {      res = 1;      break;    }    case "F": {      res = 0;      break;    }  }  return res;}function getLetterGrade(score : number) {  if(score < 1) {    return "F";  }  if(score > 3) {    return "A";  }  if(score > 2 && score < 4) {    return "B";  }  if(score >= 1 && score <= 2) {    return "C";  }  if(score > 0 && score < 2) {    return "D";  }} function gradeAverage(grades){  var total = 0;  var g = null;  var i = -1;  for(i = 0; i < grades.length; i++) {      g = grades[i];      total += getPointEquiv(grades[i]);  }  var avg = total / grades.length;  return getLetterGrade(Math.round(avg));}function getPointEquiv(grade) {  var res;  switch(grade) {    case "A": {      res = 4;      break;    }    case "B": {      res = 3;      break;    }    case "C": {      res = 2;      break;    }    case "D": {      res = 1;      break;    }    case "F": {      res = 0;      break;    }  }  return res;}function getLetterGrade(score) {  if(score < 1) {    return "F";  }  if(score > 3) {    return "A";  }  if(score > 2 && score < 4) {    return "B";  }  if(score >= 1 && score <= 2) {    return "C";  }  if(score > 0 && score < 2) {    return "D";  }}

Em seguida, abrirei um prompt de comando e usarei a ferramenta de linha de comando do TypeScript para criar o arquivo de definição e o JavaScript resultante:

tsc c:\gradeAverage.ts –declarations

O compilador cria dois arquivos: gradeAverage.d.ts é o arquivo de declaração e gradeAverage.js é o arquivo JavaScript. Em qualquer arquivo futuro do TypeScript que precise da funcionalidade gradeAverage, simplesmente adiciono uma referência no início do editor, como esta:

/// <reference path="gradeAverage.d.ts">

Em seguida, a definição de tipo e as ferramentas são realçadas ao fazer referência a essa biblioteca, e esse é o caso com qualquer uma das principais bibliotecas que você poderá encontrar no repositório DefinitelyTyped GitHub.

Um grande recurso que o compilador apresenta nos arquivos de declaração é a capacidade de autopercorrer as referências. Isso significa que se você fizer referência a um arquivo de declaração para jQueryUI que, por sua vez, faz referência a jQuery, seu arquivo atual do TypeScript obterá o benefício da conclusão da instrução e verá as assinaturas e os tipos de função como se você tivesse feito referência a jQuery diretamente. Também é possível criar um único arquivo de declaração, digamos "myRef.d.ts", que contenha as referências a todas as bibliotecas que você pretende usar na sua solução e, assim, fazer apenas uma única referência em qualquer um dos seus códigos do TypeScript.

Windows 8 e TypeScript

Com o HTML5, um cidadão de primeira classe no desenvolvimento dos aplicativos da Windows Store, os desenvolvedores querem saber se o TypeScript pode ser usado com esses tipos de aplicativos. A resposta é sim, mas é necessária alguma configuração para isso. No momento em que escrevo este artigo, as ferramentas disponíveis por meio do Instalador do Visual Studio ou outras extensões não habilitaram completamente os modelos nos modelos de aplicativo da Windows Store do JavaScript no Visual Studio 2012.

Há três arquivos de declaração principais disponíveis no código-fonte, em typescript.codeplex.com—winjs.d.ts, winrt.d.ts e lib.d.ts. Fazer referência a esses arquivos fornecerá acesso às bibliotecas JavaScript do WinJS e WinRT que são usadas nesse ambiente para acessar a câmera, os recursos do sistema e assim por diante. Você também pode adicionar referências a jQuery para obter o IntelliSense e recursos de segurança de tipo que mencionei neste artigo.

A Figura 13 é um exemplo rápido que mostra o uso dessas bibliotecas para acessar a geolocalização de um usuário e popular uma classe Location. O código então cria uma marca de imagem HTML e adiciona um mapa estático da API do Bing Map.

Figura 13 Arquivos de declaração para o Windows 8

/// <reference path="winjs.d.ts" /> /// <reference path="winrt.d.ts" /> /// <reference path="jquery.d.ts" /> module Data {   class Location {     longitude: any;     latitude: any;     url: string;     retrieved: string;   }   var locator = new Windows.Devices.Geolocation.Geolocator();   locator.getGeopositionAsync().then(function (pos) {     var myLoc = new Location();     myLoc.latitude = pos.coordinate.latitude;     myLoc.longitude = pos.coordinate.longitude;     myLoc.retrieved = Date.now.toString();     myLoc.url = "http://dev.virtualearth.net/REST/v1/Imagery/Map/Road/"       + myLoc.latitude + "," + myLoc.longitude       + "15?mapSize=500,500&pp=47.620495,-122.34931;21;AA&pp="       + myLoc.latitude + "," + myLoc.longitude       + ";;AB&pp=" + myLoc.latitude + "," + myLoc.longitude       + ";22&key=BingMapsKey";     var img = document.createElement("img");     img.setAttribute("src", myLoc.url);     img.setAttribute("style", "height:500px;width:500px;");     var p = $("p");     p.append(img);   }); };

Conclusão

Os recursos que o TypeScript adiciona ao desenvolvimento do JavaScript são pequenos, mas trazem grandes benefícios aos desenvolvedores do .NET que estão acostumados com recursos semelhantes nas linguagens que usam para o desenvolvimento regular de aplicativos do Windows.

O TypeScript não é uma bala de prata, e não tem a intenção de ser. Mas para qualquer pessoa que esteja hesitando em adentrar no JavaScript, o TypeScript é uma excelente linguagem que pode facilitar a jornada.

Shayne Boyer é MVP na Telerik, desenvolvedor sênior da Nokia, MCP, palestrante na INETA e um arquiteto de soluções em Orlando, Fla. Ele vem desenvolvendo soluções baseadas na Microsoft nos últimos 15 anos. Nos últimos 10 anos, ele trabalhou em aplicativos Web de larga escala, com um foco na produtividade e no desempenho. No seu tempo vago, Boyer administra o Grupo de Usuários Orlando Windows Phone and Windows 8 e bloga sobre a tecnologia mais recente em tattoocoder.com.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Christopher Bennage