TypeScript 初识

文章博客地址:http://pinggod.com/2016/Typescript/

TypeScript 是 JavaScript 的超集,为 JavaScript 的生态增添了范例机制,并终究将代码编译为地道的 JavaScript 代码。范例机制很重要吗?近来的一些项目阅历让我以为这真的很重要。当你陷在一个中大型项目中时(Web 运用日益成为常态),没有范例约束、范例揣摸,总有种牵一发而动全身的危急和约束。Immutable.js 和 Angular 2 都在运用 TypeScript 做开辟,它们都是体量颇大的项目,所以我决议尝试一下 Typescript。另外我们还能够尝试 Facebook 的 Flow,比较一下二者的好坏。Typescript 对 ES6 也有优越的支撑,现在组内项目运用 Babel 编译 ES6,这也就自然而然的把 TypeScirpt 和 Flow / babel-plugin-tcomb 放在了对立面,或许下一篇文章就是引见 Flow 和 babel-plugin-tcomb。

What and Why

假如你想对 TypeScript 有更深切的熟悉,那末引荐你浏览 Stack Overflow 上的问答 What is TypeScript and why would I use it in place of JavaScript? ,这一节也是对这篇问答的一个简述。

虽然 JavaScript 是 ECMAScript 范例的规范完成,但并非一切的浏览器都支撑最新的 ECAMScript 范例,这也就限制了开辟者运用最新的 JavaScript / ECMAScript 特性。TypeScript 一样支撑最新的 ECMAScript 规范,并能将代码依据需求转换为 ES 3 / 5 / 6,这也就意味着,开辟者随时能够运用最新的 ECMAScript 特性,比方 module / class / spread operator 等,而无需斟酌兼容性的题目。ECMAScript 所支撑的范例机制异常丰富,包括:interface、enum、hybird type 等等。

与 TypeScript 相似的东西言语另有很多,它们重要分为两个阵营,一个是相似 Babel 的阵营,以 JavaScript 的体式格局编写代码,致力于为开辟者供应最新的 ECMAScript 特性并将代码编译为兼容性的代码;另一个则是 Coffeescript、Clojure、Dart 等的阵营,它们的语法与 JavaScript 判然差别,但终究会编译为 JavaScript。TypeScript 在这二者之间取得了一种均衡,它既为 JavaScript 增添了新特性,也坚持了对 JavaScript 代码的兼容,开辟者险些能够直接将 .js 文件重命名为 .ts 文件,就能够运用 TypeScript 的开辟环境,这类做法一方面能够削减开辟者的迁徙本钱,一方面也能够让开辟者疾速上手 TypeScript。

JavaScript 是一门诠释型言语,变量的数据范例具有动态性,只需实行时才肯定变量的范例,这类后知后觉的认错要领会让开辟者成为调试巨匠,但无益于编程才能的提拔,还会下降开辟效力。TypeScript 的范例机制能够有用根绝由变量范例引发的误用题目,而且开辟者能够掌握对范例的监控水平,是严厉限制变量范例照样宽松限制变量范例,都取决于开辟者的开辟需求。增添范例机制以后,副作用重要有两个:增大了开辟人员的学习曲线,增添了设定范例的开辟时候。整体而言,这些支付相关于代码的健壮性和可维护性,都是值得的。

现在主流的 IDE 都为 TypeScript 的开辟供应了优越的支撑,比方 Visual Studio / VS Code、Atom、Sublime 和 WebStorm。TypeScript 与 IDE 的融会,便于开辟者及时猎取范例信息。举例来说,经由历程代码补全功用能够猎取代码库中其他函数的信息;代码编译完成后,相干信息或毛病信息会直接反应在 IDE 中……

在行将宣布的 TypeScript 2.0 版本中,将会有很多优异的特性,比方对 null 和 undefined 的搜检。cannot read property 'x' of undefinedundefined is not a function 在 JavaScript 中黑白经常见的毛病。在 TypeScript 2.0 中,经由历程运用 non-nullable 范例能够防止此类毛病:let x : number = undefined 会让编译器提醒毛病,由于 undefined 并非一个 number,经由历程 let x : number | undefined = undefinedlet x : number? = undefined 能够让 x 是一个 nullable(undefined 或 null) 的值。假如一个变量的范例是 nullable,那末 TypeScript 编译器就能够经由历程掌握流和范例剖析来剖断对变量的运用是不是平安:

let x : number?;
if (x !== undefined)
    // this line will compile, because x is checked.
    x += 1;

// this line will fail compilation, because x might be undefined.    
x += 1;

TypeScript 编译器既能够将 source map 信息置于天生的 .js 文件中,也能够建立自力的 .map 文件,便于开辟者在代码运转阶段设置断点、检察变量。另外,TypeScript 还能够运用 decorator 阻拦代码,为差别的模块体系天生模块加载代码,剖析 JSX 等。

Usage

这一节引见 TypeScirpt 的一些基础特性,算是举一反三,愿望引发人人尝试和运用 TypeScript 的兴致。起首,从最简朴的范例标注最先:

// 原始值
const isDone: boolean = false;
const amount: number = 6;
const address: string = 'beijing';
const greeting: string = `Hello World`;

// 数组
const list: number[] = [1, 2, 3];
const list: Array<number> = [1, 2, 3];

// 元组
const name: [string, string] = ['Sean', 'Sun'];

// 罗列
enum Color {
    Red,
    Green,
    Blue
};
const c: Color = Color.Green;

// 恣意值:能够挪用恣意要领
let anyTypes: any = 4;
anyTypes = 'any';
anyTypes = false

// 空值
function doSomething (): void {
    return undefined;
}

// 范例断言
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

TypeScript 中的 Interface 能够看作是一个鸠合,这个鸠合是对对象、类等内部结构的商定:

// 定义接口 Coords
// 该接口包括 number 范例的 x,string 范例的 y
// 个中 y 是可选范例,即是不是包括该属性无所谓
interface Coords {
    x: number;
    y?: string;
};

// 定义函数 where
// 该函数吸收一个 Coords 范例的参数 l
function where (l: Coords) {
    // doSomething
}

const a = { x: 100 };
const b = { x: 100, y1: 'abc' };

// a 具有 number 范例的 x,能够通报给 where
where(a);
// b 具有 number 范例的 x 和 string 范例的 y1,能够通报给 where
where(b);

// 下面这类挪用体式格局将会报错,虽然它和 where(b) 看起来是一致的
// 区分在于这里通报的是一个对象字面量
// 对象字面量会被特别看待并经由分外的属性搜检
// 假如对象字面量中存在目的范例中未声明的属性,则抛出毛病
where({ x: 100, y1: 'abc' });

// 最好的处理体式格局是为接口增添索引署名
// 增添以下所示的索引署名后,对象字面量能够有恣意数目的属性
// 只需属性不是 x 和 y,其他属性能够是 any 范例
interface Coords {
    x: number;
    y?: string;
    [propName: string]: any
};

上面的代码演示了接口对对象的约束,另外,接口还常用于约束函数的行动:

// CheckType 包括一个挪用署名
// 该挪用署名声清楚明了 getType 函数须要吸收一个 any 范例的参数,并终究返回一个 string 范例的效果
interface CheckType {
    (data: any): string;
};

const getType: CheckType = (data: any) : string => {
    return Object.prototype.toString.call(data);
}

getType('abc');
// => '[object String]'

与老牌强范例言语 C#、Java 雷同的是,Interface 也能够用于约束类的行动:

interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
    tick();
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}
class AnalogClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("tick tock");
    }
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

class

除了 ES6 增添的 Class 用法,TypeScript 还增添了 C++、Java 中常见的 public / protected / private 限制符,限制变量或函数的运用范围。TypeScript 运用的是结构性范例体系,只需两种范例的成员范例雷同,则以为这两种范例是兼容和一致的,但比较包括 private 和 protected 成员的范例时,只需他们是来自一致处的一致范例成员时才会被以为是兼容的:

class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

class Rhino extends Animal {
    constructor() { super("Rhino"); }
}

class Employee {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");

animal = rhino;
// Error: Animal and Employee are not compatible
animal = employee;

笼统类是供其他类继承的基类,与接口差别的是,笼统类能够包括成员要领的完成细节,但抽不能够包括笼统要领的完成细节:

abstract class Animal {
    // 笼统要领
    abstract makeSound(): void;
    // 成员要领
    move(): void {
        console.log('roaming the earch...');
    }
}

function

增添范例机制的 TypeScript 在函数上最能够秀的一块就是函数重载了:

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
    // Check to see if we're working with an object/array
    // if so, they gave us the deck and we'll pick the card
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
let pickedCard2 = pickCard(15);

console.log("card: " + pickedCard1.card + " of " + pickedCard1.suit);
console.log("card: " + pickedCard2.card + " of " + pickedCard2.suit);

编译器起首会尝试婚配第一个函数重载的声明,假如范例婚配胜利就实行,不然继承婚配其他的重载声明,因而参数的针对性越强的函数重载,越要靠前声明。

genrics

function identity<T>(arg: T[]): T[] {
    console.log(arg.length);
    return arg;
}

let myIdentity: {<T>(arg: T[]): T[]} = identity;

上面的代码展现了泛型的基础用法,这里的 <T> 称为泛型变量,经由历程这个声明,我们能够肯定传入的参数范例和返回的数据范例是一致的,一旦肯定了传入的参数范例,也就肯定了返回的数据范例。myIdentity 运用了带有挪用署名的对象字面量定义泛型函数,实际上能够连系接口,写出更简约的泛型接口:

interface IdentityFn {
     <T>(arg: T[]): T[];
};

let myIdentity: IdentityFn = identity;

假如一致个泛型变量在接口中被重复运用,那末能够在定义接口名的同时声明泛型变量:

interface IdentityFn<T> {
    (arg: T[]): T[];
};

function identity<T>(arg: T[]): T[] {
    console.log(arg.length);
    return arg;
}

let myIdentity: IdentityFn<string> = identity;

在泛型接口以外,还能够运用泛型类,二者的情势异常相似:

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

泛型也能够直接继承接口约束本身的行动:

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

type inference

TypeScript 重要有两种范例揣摸体式格局:Best Common Type 和 Contextual Type。我们先引见 Best Common Type:

let x = [0, 1, null];

关于上面代码中的变量 x,假如要揣摸出它的范例,就必须充分斟酌 [0, 1, null] 的范例,所以这里举行范例揣摸的递次是从表达式的恭弘=叶 恭弘子到根的,也就是先揣摸变量 x 的值都包括什么范例,然后总结出 x 的范例,是一种从下往上的揣摸历程。

TypeScript 的范例推论也能够根据从上往下的递次举行,这被称为 Contextual Type

window.onmousedown = function(mouseEvent) {
    // Error: Property 'button' does not exist ontype 'MouseEvent'
    console.log(mouseEvent.buton);  
};

在上面的示例中,TypeScript 范例揣摸机制会经由历程 window.onmousedown 函数的范例来揣摸右边函数表达式的范例,继而揣摸出 mouseEvent 的范例,这类从上到下的揣摸递次就是 Contextual Type 的特性。

这里只对 TypeScript 的特性做简朴的引见,更细致的材料请参考以下材料:

React and Webpack

在 TypeScript 中开辟 React 时有以下几点注意事项:

  • 对 React 文件运用 .tsx 的扩展名

  • 在 tsconfig.json 中运用 compilerOptions.jsx: 'react'

  • 运用 typings 范例定义

interface Props {
    foo: string;
}

class MyComponent extends React.Component<Props, {}> {
    render() {
        return <span>{this.props.foo}</span>
    }
}

<MyComponent foo="bar" />; // 准确

TypeScript 的官方文档中对 React 的开辟做了一个简朴的演示,重要包括以下几个部份:

  • 运用 tsconfig.json 作为 TypeScript 的编译配置文件

  • 运用 webpack 作为构建东西,须要装置 webpack、ts-loader 和 source-map-loader

  • 运用 typings 作为代码提醒东西

详细的搭建流程能够参考文档 React & Webpack,另外,我个人写过一个 TypeScript & Webpack & React 开辟的最小化模板可供列位参考,与之同等的 Babel & Webpack & React 版本

假如检察模板以后对 import * as React from 'react' 的体式格局有所迷惑,请检察 TypeScript 的负责人 Anders Hejlsberg 在 issue#2242 中的细致剖析。

参考材料
    原文作者:ping4god
    原文地址: https://segmentfault.com/a/1190000005970553
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞