澳门金沙30064在线网站使用TypeScript提高开发能力

By admin in 计算机教程 on 2019年6月23日

原文地址:using typescript with angularjs and web api 版权归其作者所有.

译者:张天军

原文:Improving Development with
TypeScript

本文为极客学院Wiki组织翻译,转载请注明出处。

时间:2016.3.21

在这篇文章中我将向大家展示如何使用TypeScript,Angular Js 和Asp.net Web
API 来构建一个基本的实现CRUD功能的Web应用程序.
Typescript提供了一系列的功能来方便程序员构造和组织更具有可维护性的Js应用程序.同时也可以集成现有的第三方库文件,你可以在接下来的Demo中看到这项特性.这个基本的HTML应用将借助Asp.net
Web API实现增,删,查,改功能.

世界在TypeScript的眼里是什么样子呢?你在使用TypeScript versus
ES6编程的时候有什么得失呢?

主要特点如下:

如果你一直在琢磨这个问题,那么今天我们将深入地帮你列出答案。解决这个问题的最好办法是通过代码。所以让我们来深入了解它吧。在本文中,我们将改造
Kendo UI 的示例app中的其中一个 – Layout Digram
App。我选择这个示例是因为它包含了各种排序的 Kendo UI
控制。由于我们很多人使用 Angular JS 开发,我们将继续并且从它的 jQuery
实现方式来重构(如果你不使用 Angular
,这个示例仍然是可以参考的,简单忽略特定的 Angular 位)。

  • 使用TypeScript构建AngularJs的Controller,
  • 使用TypeScript并利用AngularJs 的Ajax功能与Web API进行通信,
  • 使用TypeScript的强类型来定义AngularJs对象.

快速入门

在这个例子中,我使用VS 2012和Sublime
Text来编写代码.你也可以选择自己喜欢的文本编辑器,如果你需要语法高亮或者代码自动补全功能,你需要单独下载程序包.

环境:

TSD(TYPESCRIPT DEFINITION MANAGER)
你需要下载所有的TypeScript
definitions
。这个包含了我们项目中使用的JavaScript库的定义(AngularJS,lodash,KendoUI,等。)你可以把TSD命令行工具当做其它依赖包工具的等价物,比如Nuget,Bower,npm,等等。

安装TSD,你需要安装Node.js/npm:

npm install tsd -g

你现在可以搜索并且安装其它TypeScript包。TSD目前即包含JavaScript库的客户端定义,又包含JavaScript库的服务端定义。

例如:

tsd query angular

结果如下(简化):

- angular-agility            / angular-agility
- angular-bootstrap-lightbox / angular-bootstrap-lightbox
- angular-dialog-service     / angular-dialog-service
- angular-dynamic-locale     / angular-dynamic-locale
- angular-file-upload        / angular-file-upload
- angular-formly             / angular-formly
- angular-gettext            / angular-gettext
- angular-google-analytics   / angular-google-analytics
- angular-growl-v2           / angular-growl-v2
- angular-hotkeys            / angular-hotkeys
- angularjs                  / angular

然后你可以根据自己需求自定义安装:

tsd install angular --save

你可能会注意到TypeScript的定义文件已“d.ts”为后缀,比如 AngularJS 的
TypeScript定义文件 为 angular.d.ts 。在安装一个定义文件的时候忽略 –save
选项,如果文件不存在将创建一个tsd.d.ts文件,并且为我们项目中的每个TypeScript定义的依赖添加入口。

下面是运行命令行的目录结构图:

├── myapp/
│   ├── typings
│   │   ├── angularjs
│   │   │   ├── angular.d.ts
│   │   ├── tsd.d.ts

下面你会注意到,一条添加到Angular tsd依赖的命令是如何为我们 app/project
目录下的
tsd.d.ts
提供其他的依赖的。

/// <reference path="express/express.d.ts" />
/// <reference path="node/node.d.ts" />
/// <reference path="stylus/stylus.d.ts" />
/// <reference path="serve-favicon/serve-favicon.d.ts" />
/// <reference path="morgan/morgan.d.ts" />
/// <reference path="body-parser/body-parser.d.ts" />
/// <reference path="errorhandler/errorhandler.d.ts" />
/// <reference path="serve-static/serve-static.d.ts" />
/// <reference path="mime/mime.d.ts" />
/// <reference path="../public/lib/kendo-ui/typescript/kendo.all.d.ts" />
/// <reference path="angularjs/angular.d.ts" />
/// <reference path="angular-ui-router/angular-ui-router.d.ts" />
/// <reference path="jquery/jquery.d.ts" />

你可能会注意到列表中包含了Kendo UI tsd。有时我们下载的例如Kendo
UI,angular-ui-router,和其他包括tsd文件的JavaScript库。在这些情况下,我们可以只打开tsd.d.ts文件,并且直接引用到我们项目app/project目录(使用相对路径)。

对于VS插件和Windows下的可执行文件(tsc.exe,安装包的一部分),请访问: http://www.typescriptlang.org/\#Download.
如果你想选择一个不同的编辑器,(例如:Sublime Text或者Vim),请从这里寻找帮助
http://aka.ms/qwe1qu. TypeScript编译程序是必不可少的工具.

开始编码

正如我前面所述,本文中,我们将使用AngularJS重构Kendo UI Diagram Sample
Application
。这是一个很好的例子,因为它在案例app中使用了很多
Kendo UI组件,允许我们通过TypeScript和AngularJS使用大量的Kendo UI组件。

服务端通过Asp.net Web
API实现(.Net平台下构建Http服务的实现,我个人很喜欢),当然你也可以选择自己喜欢的技术.

别名(可选)

在TypeScript中,有一个静态输入数据类型的概念,据我所知,大部分团队通常不管他们正在敲些什么,只是完全的限定。然而这篇文章中,我们将重命名大部分
Kendo
UI,因而使他们根据简短。同样,你也可以跳过命名空间步骤的别名,并且按你所想的,对所有都完全限定。

例如,我们使用完全限定的命名空间初始化一个 ObservableArray :

var myArray = new kendo.data.ObservableArray([]);

那么,下面我们使用别名命名空间初始化一个 ObservableArray

import ObserverableArray = kendo.data.ObservableArray; // aliased
var myArray = new ObserverableArray([]); // initialized w/ alias

下一步,我们继续解决关注点分离。我们将从逻辑层和返回数据的view
model分离view(presentation),例如组件初始化和数据绑定。

使用Asp.net Web API 提供Http服务.

TypeScript接口抽象(可选)

作为一个最佳实践,我习惯为每一个 Angular Controller/ViewModel
创建一个接口,并且放在相同的文件中,作为
Controller的实现。为什么呢?这里有几个重要的原因:

  • ng
    Controller(class)的目的很明显,通过接口我们可以快速理解这个意图,用处和关注点。
  • 理解什么是ng.IScope($scope)的界限。

下面是IDiagramController接口(diagram.controller.ts):

interface IDiagramController {
    diagramWidget: Diagram;
    diagramWidgetOptions: IDiagramOptions;
    canvasBackgroundColor: string;
    selected: Array<any>;
    selectedShape: Shape;
    selectedConnection: Connection;
    diagramZoomOptions: ISliderOptions;
    menuOptions: IMenuOptions;
    uploadOptions: IUploadOptions;
    splitterOptions: ISplitterOptions;
    panelBarOptions: IPointOptions;
    colorPickerOptions: IColorPickerOptions;
    canvasLayoutOptions: IDropDownListOptions;
    connectionCapOptions: IDropDownListOptions;
    windowWidgetOptions: IWindowOptions;
    shapeItemDraggableOptions: IDraggableOptions;
    alignConfigurationOptions: IButtonOptions;
    arrangeConfigurationOptions: IButtonOptions;
    windowWidget: Kwindow;
    shapePropertiesChange: (e: JQuery) => void;
    exportClick: (e: HTMLAnchorElement) => void;
}

现在我们可以实现IDiagramController,我们尽量使用Controller/ViewModel,注意,下面,我们使用Angular注册DiagramController的地方实现这个类。我也建议使用Angular
1.x版本,因为无论何时升级到Angular v2,都会很好的兼容。

class DiagramController implements IDiagramController {

    static $inject = ['$scope', '$window'];

    constructor(private $scope: IDiagramScope, private $window: any) {
        var vm = this;           
    }
}

angular
    .module('diagram')
    .controller('diagram.DiagramController', DiagramController);

对于这个例子,我处理的模型由一个单一实体构成—-Product.

TypeScript开发时间和完美编译时间

TypeScript
的好处是所有的类都是类型安全的,而且提供了一个支持确定开发时和编译时错误提醒。当完全由TypeScript编写时,当你的类型是混合类型或者使用静态语言例如C#,Java,C++,等等实现非静态操作时,你能够获取开发时和编译时的错误。

例如,如果你使用 Visual Studio
Code,你好注意到,你能够获取接口没有被马上实现的警告,如果我们通过TypeScript获取编译错误。实际上我们使用混合类型是相同的(例如使用number声明,赋值string)。

澳门金沙30064在线网站 1

Paste_Image.png

下面TypeScript能够从声明中推断出myArray是ObservableArray
的一种类型。然而我们设置myArray 为 ObservableObjectm TypeScript
会立即指示错误位置。

澳门金沙30064在线网站 2

Paste_Image.png

//Model
public abstract class Entity  {      public Guid Id { get; set; }  }    public class Product : Entity  {      public string Name { get; set; }      public decimal Price { get; set; }  }

重构操作和KENDO MENU

让我们来看看重构代码以支持新架构的案例,首先jQuery 和JavaScript版本:

var actions = {
    blank: reset,
    undo: undo,
    redo: redo,
    copy: copyItem,
    paste: pasteItem
};

$("#menu ul").kendoMenu({
    dataSource: [
        { text: "New", spriteCssClass: "new-item", items: [
            { text: "Blank", spriteCssClass: "blank-item", cssClass: "active" }
            ]
        },
        { text: "Open<input id='upload' type='file' name='files' />", encoded: false, spriteCssClass: "open-item", cssClass: "upload-item" },
        { text: "Save<a id='export' download='diagram.json'></a>", encoded: false, spriteCssClass: "save-item" },
        { text: "Undo", spriteCssClass: "undo-item", cssClass: "active" },
        { text: "Redo", spriteCssClass: "redo-item", cssClass: "active" },
        { text: "Copy", spriteCssClass: "copy-item", cssClass: "active" },
        { text: "Paste", spriteCssClass: "paste-item", cssClass: "active" }
    ],
    select: function(e) {
        var item = $(e.item),
            itemText = item.children(".k-link").text();

        if (!item.hasClass("active")) {
            return;
        }

        actions[itemText.charAt(0).toLowerCase() + itemText.slice(1)]();
    }
});

与此相比,使用TypeScript和AngularJS 版本如下:

var actions: IMenuActions = {
    blank: (e: IMenuSelectEvent): void => {
        this.diagramWidget.clear();
    },
    undo: (e: IMenuSelectEvent): void => {
        this.diagramWidget.undo();
    },
    redo: (e: IMenuSelectEvent): void => {
        this.diagramWidget.redo();
    },
    copy: (e: IMenuSelectEvent): void => {
        this.diagramWidget.copy();
    },
    paste: (e: IMenuSelectEvent): void => {
        this.diagramWidget.paste();
    }
};

vm.menuOptions = {
    dataSource: [
        {
            text: "New", spriteCssClass: "new-item", items: [
                { text: "Blank", spriteCssClass: "blank-item", cssClass: "active" }
            ]
        },
        { text: "Open<input kendo-upload='upload' type='file' name='files' k-options='vm.uploadOptions' />", encoded: false, spriteCssClass: "open-item", cssClass: "upload-item" },
        { text: "Save<a id='export' download='diagram.json' ng-click='vm.exportClick($event)'></a>", encoded: false, spriteCssClass: "save-item" },
        { text: "Undo", spriteCssClass: "undo-item", cssClass: "active" },
        { text: "Redo", spriteCssClass: "redo-item", cssClass: "active" },
        { text: "Copy", spriteCssClass: "copy-item", cssClass: "active" },
        { text: "Paste", spriteCssClass: "paste-item", cssClass: "active" }
    ],
    select: (e: IMenuSelectEvent) => {
        var item = angular.element(e.item),
            itemText = item.children(".k-link").text();

        if (!item.hasClass("active")) {
            return;
        }
        actions[itemText.charAt(0).toLowerCase() + itemText.slice(1)](e);
    }
};

我们需要一个持久化机制来存储这些实体.我选择使用仓储模式把这些内容存在内存里.请根据需要,随意替换成适合你的东西(例如:Entity
Framework或者Nhibernate.)

重构SHAPEPROPERTIES变化事件

这里,我们将重构ShapeProperties变化事件,当其中一个属性(颜色,形状等)改变时,会同步更改选中对象的设计显示。

首先,jQuery和JavaScript版本:

$("#shapeProperties").on("change", shapePropertiesChange);

function shapePropertiesChange() {
    var elements = selected || [],
        options = {
            fill: $("#shapeBackgroundColorPicker").getKendoColorPicker().value(),
            stroke: {
                color: $("#shapeStrokeColorPicker").getKendoColorPicker().value(),
                width: $("#shapeStrokeWidth").getKendoNumericTextBox().value()
            }
        },
        bounds = new Rect(
            $("#shapePositionX").getKendoNumericTextBox().value(),
            $("#shapePositionY").getKendoNumericTextBox().value(),
            $("#shapeWidth").getKendoNumericTextBox().value(),
            $("#shapeHeight").getKendoNumericTextBox().value()
        ),
        element, i;

    for (i = 0; i < elements.length; i++) {
        element = elements[i];
        if (element instanceof Shape) {
            element.redraw(options);

            element.bounds(bounds);
        }
    }
}

下面让我来看看 TypeScript和AngularJS版本:

    <li>
    Background Color:
    <input kendo-color-picker 
        ng-model="vm.selectedShape.options.fill" 
        k-on-change="vm.shapePropertiesChange(kendoEvent)" />
    </li>
    <li>
    Stroke Color:
    <input 
        kendo-color-picker 
        ng-model="vm.selectedShape.options.stroke.color" 
        k-on-change="vm.shapePropertiesChange(kendoEvent)" />
    </li>
    <li>
    Stroke Width:
    <input kendo-numeric-text-box type="text" 
        k-ng-model="vm.selectedShape.options.stroke.width"
        k-on-change="vm.shapePropertiesChange(kendoEvent)" />
    </li>

<!-- code shortened for brevity-->

请看diagram.controller.ts,你会发现借助于Angular的MVVM优势,我们再也不需使用jQuery
selectors 去拼凑UI控件。现在我们直接绑定View到ViewModel:

    public shapePropertiesChange = (e: JQuery): void => {
    var elements = this.selected || [];
    var i: number, element;

    elements.forEach((element) => {
        if (element instanceof Shape) {
            var shape = element;

            shape.redraw({
                fill: this.selectedShape.options.fill,
                stroke: this.selectedShape.options.stroke
            });

            shape.bounds(
                this.selectedShape.height,
                this.selectedShape.width,
                this.selectedShape.x,
                this.selectedShape.y
            );
        }
    });
    };
// IRepository
public interface IRepository<TEntity>      where TEntity : Entity  {      TEntity Add(TEntity entity);      TEntity Delete(Guid id);      TEntity Get(Guid id);      TEntity Update(TEntity entity);      IQueryable<TEntity> Items { get; }  }

//InMemoryRepository  public class InMemoryRepository<TEntity> : IRepository<TEntity> where TEntity : Entity  {      private readonly ConcurrentDictionary<Guid, TEntity> _concurrentDictionary           = new ConcurrentDictionary<Guid, TEntity>();        public TEntity Add(TEntity entity)      {          if (entity == null)          {              //we dont want to store nulls in our collection              throw new ArgumentNullException("entity");          }            if (entity.Id == Guid.Empty)          {              //we assume no Guid collisions will occur              entity.Id = Guid.NewGuid();          }            if (_concurrentDictionary.ContainsKey(entity.Id))          {              return null;          }            bool result = _concurrentDictionary.TryAdd(entity.Id, entity);            if (result == false)          {              return null;          }          return entity;      }        public TEntity Delete(Guid id)      {          TEntity removed;          if (!_concurrentDictionary.ContainsKey(id))          {              return null;          }          bool result = _concurrentDictionary.TryRemove(id, out removed);          if (!result)          {              return null;          }          return removed;      }        public TEntity Get(Guid id)      {          if (!_concurrentDictionary.ContainsKey(id))          {              return null;          }          TEntity entity;          bool result = _concurrentDictionary.TryGetValue(id, out entity);          if (!result)          {              return null;          }          return entity;      }        public TEntity Update(TEntity entity)      {          if (entity == null)          {              throw new ArgumentNullException("entity");          }          if (!_concurrentDictionary.ContainsKey(entity.Id))          {              return null;          }          _concurrentDictionary[entity.Id] = entity;          return entity;      }        public IQueryable<TEntity> Items      {          get { return _concurrentDictionary.Values.AsQueryable(); }      }  }

ES6 & ES7

你可能会注意到,我们使用到了TypeScript中ES6/ECMA6的新功能。例如,我们在上述forEach方法(aka
fat arrows 和lambdas)中使用了箭头的功能。

随着最近发布的TypeScript
v1.7x
,我们如今甚至能够使用ES7/ECMA7新功能开发,这个新功能可以兼容很多浏览器,即使不支持ES6或ES7。

一旦我们在这里完成了对象持久化,我们就可以创造一个Http服务,来提供一些基本的操作.我使用的是Asp.net
Web API,所以我还要再创建一个 Controller.

INTELLISENSE

此外,即使是Kendo UI类型我们也可以获取
Intellisense,因为我们声明了selectedShape 并且定义为Kendo UI Shape
类型。

    var selectedShape: kendo.dataviz.diagram.Shape;

澳门金沙30064在线网站 3

Paste_Image.png

显然,这将是和所有你导入的TSD库类型相似的,包含了jQuary,Angular 和
loadsh。

此外,我们现在可以实现一个真正的“查找所有依赖”
或者“查找所有使用”,举例来说,你能够为我们在ViewModel或者Angular控制器中的selectedShape实现一个“查找所有依赖”。如果这是用于工程间,我们也能够跨工程获取结果列表。

澳门金沙30064在线网站 4

Paste_Image.png

如果你使用Visiual Studio
Code,则能够打开“peek”视图,并且在右侧列出所有使用this.selectedShape的列表。你能够通过点击浏览每一个出现的地方,而且通过右侧的视图列表浏览也能够自动的滚动到出现的地方。

澳门金沙30064在线网站 5

Paste_Image.png

其它存在TypeScript特性的有:

  • Solution-wide 重构
  • Peek定义
  • Go to定义

值得注意的是,这些特不仅Visual Studio
Code独有的。在大多数支持TypeScript的IDE都可以使用。由于为JavaScript提供了强大的开发和编译时体验,因此TypeScrip带来了很多好处,可以提高你的开发效率。

//Product HTTP Service  public class ProductsController : ApiController  {      public static IRepository<Product> ProductRepository          = new InMemoryRepository<Product>();        public IEnumerable<Product> Get()      {          return ProductRepository.Items.ToArray();      }        public Product Get(Guid id)      {          Product entity = ProductRepository.Get(id);          if (entity == null)          {              throw new HttpResponseException(HttpStatusCode.NotFound);          }          return entity;      }        public HttpResponseMessage Post(Product value)      {          var result = ProductRepository.Add(value);          if (result == null)          {              // the entity with this key already exists              throw new HttpResponseException(HttpStatusCode.Conflict);          }          var response = Request.CreateResponse<Product>(HttpStatusCode.Created, value);          string uri = Url.Link("DefaultApi", new { id = value.Id });          response.Headers.Location = new Uri(uri);          return response;      }        public HttpResponseMessage Put(Guid id, Product value)      {          value.Id = id;          var result = ProductRepository.Update(value);          if (result == null)          {              // entity does not exist              throw new HttpResponseException(HttpStatusCode.NotFound);          }          return Request.CreateResponse(HttpStatusCode.NoContent);      }        public HttpResponseMessage Delete(Guid id)      {          var result = ProductRepository.Delete(id);          if (result == null)          {              throw new HttpResponseException(HttpStatusCode.NotFound);          }          return Request.CreateResponse(HttpStatusCode.NoContent);      }  }

总结

我已经上传了文中介绍用TypeScript和AngularJS重构Kendo UI Diagram
Application的sample到Github上。你可以访问。我也已经部署已完成和已经重构好的应用,地址

在接下来的文章中,我打算使用Kendo替换TypeScript和Angular2 TypeScript
with NativeScript。可以通过Twitter@lelong37 给我意见反馈或者直接留言。

祝编码愉快!

我们努力去实现标准的Http,因此附加逻辑来处理
Response.在这个步骤结束后,我们将会有一个功能完整的,(实现了CRUD)Http服务.

澳门金沙30064在线网站 6

 

开始使用Angular Js 和 TypeScript

服务端创建好以后,我们接着开始创建真正的网站部分.它将完全由Html/CSS/Js(TypeScript将会编译成Js)构成.我选择的初始模版像这样:

<!DOCTYPE html>  <html ng-app>  <head>      <title>Product list</title>      <link rel="stylesheet" href="Content/bootstrap.css"/>      <script type="text/javascript" src="Scripts/bootstrap.js"></script>      <script type="text/javascript" src="Scripts/angular.js"></script>      <script type="text/javascript" src="Scripts/Controllers/ProductsController.js"></script>  </head>      <body>          <div ng-controller="Products.Controller">          </div>      </body>  </html>

 

请注意:ProductsController.js文件是从名为ProductsController.ts的TypeScript源代码通过tsc.exe编译得到的.(我使用了命令行来执行这个编译步骤.).让我们为这个页面创建一个Angular
Js的Controller.

 

//ProductsController.ts  module Products {      export interface Scope {          greetingText: string;      }        export class Controller {          constructor ($scope: Scope) {              $scope.greetingText = "Hello from TypeScript + AngularJS";          }      }  }

 

Produscts Module将被编译成一个 Js的命名空间,我们的Controller将被转译为
Products.Controller供程序使用.现在我们可以在页面中绑定一个欢迎致辞.请注意:结合TypeScript的特点,我们以Scope接口的形式定义了$scope对象.Typescript也允许我们使用
any
这种类型来定义,但就个人而言,我更倾向于使用更加严格的定义方式,因为他可以帮助你在编译时捕获错误信息.(VS2012甚至会在你编写的错误代码下面加入红色下划线,这真是太棒了.)现在,我们需要编译ProductsController.ts,在HTML中引用ProductsController.js,并且修改视图(view)来显示信息.

<div ng-controller="Products.Controller">      <p>{{greetingText}}</p>  </div>

澳门金沙30064在线网站 7

现在AngularJs Controller已经完成,让我们继续添加更多的功能.

创建Model模块

我们将创建一个Model模块,它会包含一个Product类,作为DTO对象(序列化为JSON)与Http的服务端进行交互.

 

module Model {      export class Product {          Id: string;          Name: string;          Price: number;      }  }

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2019 澳门金沙30064在线网站 版权所有