TypeScript 学习笔记

该篇笔记主要记录与Javascript相比新增的相关概念和功能

1.基础类型

布尔值

let a: boolean = false;

数字

let b: number = 123; 

字符串

let c: string = "zjw";

数组

let d: number[] = [1,2,3];
let d: Array<number> = [1,2,3];

元组,元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同

let e: [string, number] = ['hello', 10];  //对应位置的元素类型必须一致
e[3] = 'world';  //越界元素可以是联合类型(string | number)

枚举, 使用枚举类型可以为一组数值赋予友好的名字,默认情况下,从0开始为元素编号作为元素枚举值,你可以由枚举值得到元素的名字

enum Color {Red = 1, Green, Blue}
let f: Color = Color.Green;  // 2
let colorName: string = Color[2];  // 'Green'

任何类型

let g: any = 4;
let h: any[] = [1, 'a', true];

没有任何类型, 只能赋值undefiened和null

function a(): void {
	console.log(1,2,3)
}

object表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型

let o: object = {a: 1};

类型断言

// 尖括号形式
let strLen: number = (<string>value).length;

// as语法
let strLen: num = (value as string).length;

2.接口

普通接口

interface myObj {
	label: string;
	color?: string;        //可选属性
	readonly x: number;    //只读属性, 创建后就不能再修改
	[propName: string]: any;    //其他属性
}

function printLabel(Obj: myObj) {
  console.log(Obj.label);
}

let obj = {size: 10, label: "Size 10 Object"};

printLabel(myObj);

函数类型

interface SearchFunc {
	(source: string, subString: string): boolean;
}

let mySearch: SearchFunc;

mySearch = function(src: string, sub: string): boolean {
  let result = src.search(sub);
  return result > -1;
}

可索引的类型

可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。TypeScript支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。

interface NumberDictionary {
  [index: string]: number;
  length: number;    // 可以,length是number类型
  readonly [index: number]: number;
}

let map: NumberDictionary = {a: 1, length: 2, 0: 3};

类类型
与C#或Java里接口的基本作用一样,TypeScript也能够用它来明确的强制一个类去符合某种契约

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

对于类的静态部分,例如构造函数,不能直接通过类实施接口进行检查,需要提出处理

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");
    }
}

let digital = createClock(DigitalClock, 12, 17);

接口可以互相继承,包括多重继承

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: number;
}

混合类型
接口可以描述丰富的类型

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

接口继承类
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。这意味着当你创建了一个接口继承了一个拥有私有(private)或受保护的成员(protected)的类时,这个接口类型只能被这个类或其子类所实现(implement)。

class Control {
    private state: any;
}

interface SelectableControl extends Control {   //继承了类的接口
    select(): void;
}

class Button extends Control implements SelectableControl {  //正常
    select() { }
}


class Image implements SelectableControl {    // 错误:“Image”类型缺少“state”属性。
    select() { }
}

3.类

公共,私有与受保护的修饰符
在TypeScript里,类的成员都默认为 public,可以自由访问,当成员被标记成 private时,它就不能在声明它的类的外部访问。

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

new Animal("Cat").name; // 错误: 'name' 是私有的.

protected修饰符与 private修饰符的行为很相似,但有一点不同, protected成员在派生类中仍然可以访问。若构造函数被标记成 protected。 这意味着这个类不能在包含它的类外被实例化,但是能被继承。

class Person {
    protected name: string;
    constructor(name: string) { this.name = name; }
}

class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name)
        this.department = department;
    }

    public getElevatorPitch() {   //可以在子类的内部访问到保护的属性
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 错误

readonly修饰符
你可以使用readonly关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。

class Octopus {
    readonly name: string;
    readonly numberOfLegs: number = 8;
    constructor (theName: string) {
        this.name = theName;
    }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的.

存取器
可以在类中设置get/set,控制成员的访问

let passcode = "secret passcode";

class Employee {
    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }

    set fullName(newName: string) {
        if (passcode && passcode == "secret passcode") {
            this._fullName = newName;
        }
        else {
            console.log("Error: Unauthorized update of employee!");
        }
    }
}

let employee = new Employee();
employee.fullName = "Bob Smith";

抽象类
抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。抽象类中的抽象方法不包含具体实现并且必须在派生类中实现

abstract class Animal {
    abstract makeSound(): void;   //抽象方法,必须在子类中实现
    move(): void {
        console.log('roaming the earch...');
    }
}

4.函数

为函数的参数和返回值添加类型
如果函数没有返回任何值,你也必须指定返回值类型为void而不能留空。在TypeScript里我们可以在参数名旁使用?实现可选参数的功能,可选参数必须跟在必须参数后面。

function add(x: number, y?: number): number {
    return x + y;
}

let myAdd = function(x: number, y?: number): number { return x + y; };

重载
可以为同一个函数提供多个函数类型定义来进行函数重载,它查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面

function pickCard(x: {suit: string; card: number; }[]): number;  //定义1
function pickCard(x: number): {suit: string; card: number; };  //定义2

// 函数实现
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 };
    }
}

5.泛型

可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <T>(arg: T) => T = identity;

泛型接口

interface GenericIdentityFn {
    <T>(arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn = identity;

把泛型参数当作整个接口的一个参数

interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

泛型类
泛型类使用<>括起泛型类型,跟在类名后面

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

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

可以定义一个接口interface来描述约束条件,使用这个接口和extends关键字来实现约束

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型

function create<T>(c: {new(): T; }): T {
    return new c();
}

6.枚举

数字枚举
定义了一个数字枚举, Up使用初始化为 1。 其余的成员会从 1开始自动增长

enum Direction {
    Up = 1,
    Down,
    Left,
    Right
}

字符串枚举

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}

计算的和常量成员
每个枚举成员都带有一个值,它可以是常量或计算出来的。规则如下:

1.一个枚举表达式字面量(主要是字符串字面量或数字字面量)
2.一个对之前定义的常量枚举成员的引用(可以是在不同的枚举类型中定义的)
3.带括号的常量枚举表达式
4.一元运算符 +, -, ~其中之一应用在了常量枚举表达式
5.常量枚举表达式做为二元运算符 +, -, *, /, %, <<, >>, >>>, &, |, ^的操作对象。 若常数枚举表达式求值后为 NaN或 Infinity,则会在编译阶段报错。

//常量
enum E1 { X, Y, Z }
enum E2 {
    A = 1, B, C
}
enum FileAccess {
    // constant members
    None,
    Read    = 1 << 1,
    Write   = 1 << 2,
    ReadWrite  = Read | Write,
    // computed member
    G = "123".length
}

反向映射
创建一个以属性名做为对象成员的对象之外,数字枚举成员还具有了反向映射,从枚举值到枚举名字

enum Enum {
    A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

const枚举
常量枚举只能使用常量枚举表达式,不允许包含计算成员

const enum Enum {
    A = 1,
    B = A * 2
}

7.高级类型

交叉类型
我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。我们大多是在混入(mixins)或其它不适合典型面向对象模型的地方看到交叉类型的使用。用符号&连接不同的类型

function extend<T, U>(first: T, second: U): T & U {
    let result = <T & U>{};    //交叉类型
    for (let id in first) {
        (<any>result)[id] = (<any>first)[id];
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            (<any>result)[id] = (<any>second)[id];
        }
    }
    return result;
}

class Person {
    constructor(public name: string) { }
}
interface Loggable {
    log(): void;
}
class ConsoleLogger implements Loggable {
    log() {
        // ...
    }
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();

联合类型
联合类型用|分隔多个类型,例如string | number,代表可以是stringnumber类型

function padLeft(value: string, padding: string | number) {
    // ...
}

如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet(): Fish | Bird {
    // ...
}

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors

类型保护
类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个 类型谓词

function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>pet).swim !== undefined;
}

Null
TypeScript具有两种特殊的类型, nullundefined,默认情况下,类型检查器认为 nullundefined可以赋值给任何类型。--strictNullChecks标记可以解决此错误:当你声明一个变量时,它不会自动地包含 nullundefined, 且函数可选参数和对象可选属性也会被自动地加上 | undefined

let s = "foo";
s = null; // 错误, 'null'不能赋值给'string'

let sn: string | null = "bar";
sn = null; // 可以
sn = undefined; // error, 'undefined'不能赋值给'string | null'
function f(x: number, y?: number) {
    return x + (y || 0);
}
f(1, 2);
f(1);
f(1, undefined);
f(1, null); // error, 'null' is not assignable to 'number | undefined'

class C {
    a: number;
    b?: number;
}
let c = new C();
c.a = 12;
c.a = undefined; // error, 'undefined' is not assignable to 'number'
c.b = 13;
c.b = undefined; // ok
c.b = null; // error, 'null' is not assignable to 'number | undefined'

可以添加后缀,从变量类型中去除nullundefined

identifier!

类型别名
类型别名会给一个类型起个新名字,可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。类型别名还可以用于泛型。其与接口的区别在于不能被 extendsimplements

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;  //联合类型
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    }
    else {
        return n();
    }
}

// T可以是别名,在别名声明的右侧传入
type Container<T> = { value: T };

字符串字面量类型
字符串字面量类型允许你指定字符串必须的固定值,实现类似枚举类型的字符串

type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
    animate(dx: number, dy: number, easing: Easing) {
        if (easing === "ease-in") {
            // ...
        }
        else if (easing === "ease-out") {
        }
        else if (easing === "ease-in-out") {
        }
        else {
            // error! should not pass null or undefined.
        }
    }
}

let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here

可辨识联合
你可以合并单例类型,联合类型,类型保护和类型别名来创建一个叫做可辨识联合的高级模式。个人理解就是联合各个接口,然后这些接口都具有一些共同的属性,这些共同属性称为可辨识的特征标签

interface Square {
    kind: "square";
    size: number;
}
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
interface Circle {
    kind: "circle";
    radius: number;
}

type Shape = Square | Rectangle | Circle;

function area(s: Shape) {  // 使用可辨识联合类型
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.height * s.width;
        case "circle": return Math.PI * s.radius ** 2;
    }
}

索引类型
通过索引类型查询keyof T和 索引访问操作符T[K]可以检查属性名是否存在于对象中

function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
  return names.map(n => o[n]);
}

interface Person {
    name: string;
    age: number;
}
let person: Person = {
    name: 'Jarid',
    age: 35
};

let personProps: keyof Person; // 'name' | 'age'
let strings: string[] = pluck(person, ['name']); // ok, string[]
let strings: string[] = pluck(person, ['age', 'unknown']); // error, 'unknown' is not in 'name' | 'age'

映射类型
TypeScript提供了从旧类型中创建新类型的一种方式,新类型以相同的形式去转换旧类型里每个属性

type Readonly<T> = {
    readonly [P in keyof T]: T[P];   // 每个属性都转化为readonly
}
type Partial<T> = {
    [P in keyof T]?: T[P];     // 每个属性都转化为可选
} 

type PersonPartial = Partial<Person>;
type ReadonlyPerson = Readonly<Person>;

TypeScript 2.8在lib.d.ts里增加了一些预定义的有条件类型

Exclude<T, U> --T中剔除可以赋值给U的类型。
Extract<T, U> -- 提取T中可以赋值给U的类型。
NonNullable<T> --T中剔除null和undefined。
ReturnType<T> -- 获取函数返回值类型。
InstanceType<T> -- 获取构造函数类型的实例类型。

type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "b" | "d"
type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "a" | "c"
type T04 = NonNullable<string | number | undefined>;  // string | number

function f1(s: string) {
    return { a: 1, b: s };
}

class C {
    x = 0;
    y = 0;
}

type T10 = ReturnType<() => string>;  // string
type T14 = ReturnType<typeof f1>;  // { a: number, b: string }
type T20 = InstanceType<typeof C>;  // C

8.模块

导出
任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加export关键字来导出,还可以通过as关键字对导出的部分重命名

export interface StringValidator {
    isAcceptable(s: string): boolean;
}


class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
export { ZipCodeValidator };
export { ZipCodeValidator as mainValidator }   //重命名

重新导出,重新导出功能并不会在当前模块导入那个模块或定义一个新的局部变量

export class ParseIntBasedZipCodeValidator {
    isAcceptable(s: string) {
        return s.length === 5 && parseInt(s).toString() === s;
    }
}

// 导出原先的验证器但做了重命名
export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator";

export * from "./StringValidator"; // exports interface StringValidator

导入
可以使用以下import形式之一来导入其它模块中的导出内容

import { ZipCodeValidator } from "./ZipCodeValidator";
let myValidator = new ZipCodeValidator();

import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();

export =import = require()
为了支持CommonJS和AMD的exports, TypeScript提供了export =语法。
export =语法定义一个模块的导出对象。 这里的对象一词指的是类,接口,命名空间,函数或枚举。若使用export =导出一个模块,则必须使用TypeScript的特定语法import module = require("module")来导入此模块。

let numberRegexp = /^[0-9]+$/;
class ZipCodeValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
export = ZipCodeValidator;
import zip = require("./ZipCodeValidator");
// Validators to use
let validator = new zip();

生成模块代码
根据编译时指定的模块目标参数--module 模块类型,编译器会生成相应的模块加载系统使用的代码(例如CommonJS、ES6等)

tsc --module commonjs Test.ts

使用其它的JavaScript库
通过将暴露的API定义在.d.ts文件中,来创建外部模块

declare module "url" {
    export interface Url {
        protocol?: string;
        hostname?: string;
        pathname?: string;
    }

    export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url;
}

declare module "path" {
    export function normalize(p: string): string;
    export function join(...paths: any[]): string;
    export let sep: string;
}

在导入时添加/// <reference>,即可直接导入

/// <reference path="node.d.ts"/>
import * as URL from "url";
let myUrl = URL.parse("http://www.typescriptlang.org");

模块解析
模块解析是指编译器在查找导入模块内容时所遵循的流程。共有两种可用的模块解析策略:NodeClassic。 你可以使用 --moduleResolution标记来指定使用哪种模块解析策略。若未指定,那么在使用了 --module AMD | System | ES2015时的默认值为Classic,其它情况时则为Node

Classic策略

// 相对路径导入 /root/src/folder/A.ts中:import { b } from "./moduleB"
查找路径为:
1./root/src/folder/moduleB.ts
2./root/src/folder/moduleB.d.ts

// 非相对路径导入,编译器则会从包含导入文件的目录开始依次向上级目录遍历/root/src/folder/A.ts中:import { b } from "moduleB"
查找路径为:
1./root/src/folder/moduleB.ts
2./root/src/folder/moduleB.d.ts
3./root/src/moduleB.ts
4./root/src/moduleB.d.ts
5./root/moduleB.ts
6./root/moduleB.d.ts
7./moduleB.ts
8./moduleB.d.ts

Node策略
TypeScript是模仿Node.js运行时的解析策略来在编译阶段定位模块定义文件

// 相对路径导入 /root/src/moduleA.ts中:import { b } from "./moduleB"
查找路径为:
1./root/src/moduleB.ts
2./root/src/moduleB.tsx
3./root/src/moduleB.d.ts
4./root/src/moduleB/package.json (如果指定了"types"属性)
5./root/src/moduleB/index.ts
6./root/src/moduleB/index.tsx
7./root/src/moduleB/index.d.ts

// 非相对路径导入,编译器则会从包含导入文件的目录开始依次向上级目录遍历,查找是否存在node_modules下的模块/root/src/moduleA.ts中:import { b } from "moduleB"
查找路径为:
1./root/src/node_modules/moduleB.ts
2./root/src/node_modules/moduleB.tsx
3./root/src/node_modules/moduleB.d.ts
4./root/src/node_modules/moduleB/package.json (如果指定了"types"属性)
5./root/src/node_modules/moduleB/index.ts
6./root/src/node_modules/moduleB/index.tsx
7./root/src/node_modules/moduleB/index.d.ts

8./root/node_modules/moduleB.ts
9./root/node_modules/moduleB.tsx
10./root/node_modules/moduleB.d.ts
11./root/node_modules/moduleB/package.json (如果指定了"types"属性)
12./root/node_modules/moduleB/index.ts
13./root/node_modules/moduleB/index.tsx
14./root/node_modules/moduleB/index.d.ts

15./node_modules/moduleB.ts
16./node_modules/moduleB.tsx
17./node_modules/moduleB.d.ts
18./node_modules/moduleB/package.json (如果指定了"types"属性)
19./node_modules/moduleB/index.ts
20./node_modules/moduleB/index.tsx
21./node_modules/moduleB/index.d.ts

tsconfig.json
baseUrl来告诉编译器到哪里去查找模块。 所有非相对模块导入都会被当做相对于 baseUrl。通过"paths"我们还可以指定复杂的映射

{
  "compilerOptions": {
    "baseUrl": "./src", // This must be specified if "paths" is.
    "paths": {
      "jquery": ["../node_modules/jquery/dist/jquery"] // 此处映射是相对于"baseUrl",最终导入路径为node_modules/jquery/dist/jquery
    }
  }
}

它告诉编译器所有匹配"*"(所有的值)模式的模块导入会在以下两个位置查找:

1."*": 表示名字不发生改变,所以映射为<moduleName> => <baseUrl>/<moduleName>
2."generated/*"表示模块名添加了“generated”前缀,所以映射为<moduleName> => <baseUrl>/generated/<moduleName>

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "*": [
        "*",
        "generated/*"
      ]
    }
  }
}

有时多个目录下的工程源文件在编译时会进行合并放在某个输出目录下。 这可以看做一些源目录创建了一个“虚拟”目录。可以使用"rootDirs"来告诉编译器。 "rootDirs"指定了一个roots列表,列表里的内容会在运行时被合并

{
  "compilerOptions": {
    "rootDirs": [
      "src/views",
      "generated/templates/views"
    ]
  }
}

编译命令添加--traceResolution可输出编译过程错误

tsc --traceResolution

你可以利用“exclude”排除某些文件,或你想指定所有要编译的文件列表,请使用“files”。要从编译列表中“exclude”排除一个文件,你需要在排除它的同时,还要排除所有对它进行import或使用了/// <reference path="..." />指令的文件

9.命名空间

为了不使对象、接口的命名产生冲突,我们可以加入命名空间,并且使用export导出那些需要在命名空间外访问的对象。

namespace Validation {       //命名空间
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }

    const lettersRegexp = /^[A-Za-z]+$/;
    const numberRegexp = /^[0-9]+$/;

    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }

    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}

// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

同一命名空间可以分割到多个文件,通过加入引用标签来表示关联,最后在编译时使用--outFile标记

namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
}
/// <reference path="Validation.ts" />
namespace Validation {
    const lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
}
// Test.ts
/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />

// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
tsc --outFile sample.js Test.ts

命名空间和模块
像命名空间一样,模块可以包含代码和声明。 不同的是模块可以声明它的依赖。模块会把依赖添加到模块加载器上,这一点点的花费会带来长久的模块化和可维护性上的便利。模块文件本身已经是一个逻辑分组,不要对模块使用命名空间

10.JSX

TypeScript支持内嵌,类型检查以及将JSX直接编译为JavaScript。想要使用JSX必须做两件事:

  1. 给文件一个.tsx扩展名。
  2. 启用jsx选项

TypeScript具有三种JSX模式:preservereactreact-native。你可以通过在命令行里使用--jsx标记或tsconfig.json里的选项来指定模式。
在这里插入图片描述
类型检查
一个JSX表达式可能是环境自带的东西(例如DOM环境里的divspan),也可能是自定义的组件。前者称为固有元素,后者称为基于值的元素

对于React,固有元素会生成字符串(React.createElement("div")),然而由你自定义的组件却不会生成(React.createElement(MyComponent))。
传入JSX元素里的属性类型的查找方式不同。 固有元素属性本身就支持,然而自定义的组件会自己去指定它们具有哪个属性。

对于固有元素,使用特殊的接口JSX.IntrinsicElements来查找, 默认地,如果这个接口没有指定,会全部通过,不对固有元素进行类型检查。

declare namespace JSX {
    interface IntrinsicElements {
        foo: any
    }
}

<foo />; // 正确
<bar />; // 错误

对于基于值的元素,会简单的在它所在的作用域里按标识符查找

import MyComponent from "./myComponent";
                                
<MyComponent />; // 正确
<SomeOtherComponent />; // 错误

我们可以扩展JSX.ElementClass接口,来限制基于值的元素的实例类型,默认的JSX.ElementClass{}

declare namespace JSX {
    interface ElementClass {
    	render: any;
    }
}

class MyComponent {   //构造函数创建类
    render() {}
}
function MyFactoryFunction() {   //工厂函数创建类
    return { render: () => {} }
}

<MyComponent />; // 正确
<MyFactoryFunction />; // 正确

class NotAValidComponent {}
function NotAValidFactoryFunction() {
    return {};
}

<NotAValidComponent />; // 错误
<NotAValidFactoryFunction />; // 错误

属性类型检查
对于固有元素,这是JSX.IntrinsicElements属性的类型。

declare namespace JSX {
    interface IntrinsicElements {
    	foo: { bar?: boolean }
    }
}

// `foo`的元素属性类型为`{bar?: boolean}`
<foo bar />;

对于基于值的元素,则通过扩展JSX.ElementAttributesProperty指定用来使用的属性,而未指定JSX.ElementAttributesProperty,那么将使用类元素构造函数或无状态函数组件调用的第一个参数的类型

declare namespace JSX {
    interface ElementAttributesProperty {
    props; // 指定用来使用的属性名
    }
}

class MyComponent {
    // 在元素实例类型上指定属性
    props: {
    foo?: string;
    }
}

// `MyComponent`的元素属性类型为`{foo?: string}`
<MyComponent foo="bar" />

// 延展操作符
var props = { foo: 'bar' };
<MyComponent {...props} />; // 正确

另外,JSX还会使用JSX.IntrinsicAttributes接口来指定额外的属性,这些额外的属性通常不会被组件的propsarguments使用 - 比如React里的key。还有,JSX.IntrinsicClassAttributes<T>泛型类型也可以用来做同样的事情。这里的泛型参数表示类实例类型。在React里,它用来允许Ref<T>类型上的ref属性。

子孙类型检查
children是元素属性(attribute)类型的一个特殊属性,子JSXExpression将会被插入到属性里。我们可以利用JSX.ElementChildrenAttribute来决定children

declare namespace JSX {
    interface ElementChildrenAttribute {
    	children: {};  // specify children name to use
    }
}

如不特殊指定子孙的类型,我们将使用React typings里的默认类型

interface PropsType {
    children: JSX.Element
    name: string
}

class Component extends React.Component<PropsType, {}> {
    render() {
        return (
            <h2>
            {this.props.children}
            </h2>
        )
    }
}

// OK
<Component>
    <h1>Hello World</h1>
</Component>

// Error: children is of type JSX.Element not array of JSX.Element
<Component>
    <h1>Hello World</h1>
    <h2>Hello World</h2>
</Component>

// Error: children is of type JSX.Element not array of JSX.Element or string.
<Component>
    <h1>Hello</h1>
    World
</Component>

React整合
要想一起使用JSX和React,你应该使用React类型定义。

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

interface Props {
    foo: string;
}

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

<MyComponent foo="bar" />; // 正确
<MyComponent foo={0} />; // 错误

11.三斜线指令

三斜线指令是包含单个XML标签的单行注释。 注释的内容会做为编译器指令使用。三斜线指令仅可放在包含它的文件的最顶端。如果指定了--noResolve编译选项,三斜线引用会被忽略。

/// <reference path="..." />
三斜线指令中最常见的一种。 它用于声明文件间的依赖。三斜线引用告诉编译器在编译过程中要引入的额外的文件。

/// <reference types="..." />
/// <reference path="..." />指令相似,这个指令是用来声明依赖的。例如,把 /// <reference types="node" />引入到声明文件,表明这个文件使用了 @types/node/index.d.ts里面声明的名字; 并且,这个包需要在编译阶段与声明文件一起被包含进来

/// <reference no-default-lib="true"/>
这个指令把一个文件标记成默认库。 你会在 lib.d.ts文件和它不同的变体的顶端看到这个注释,这个指令告诉编译器在编译过程中不要包含这个默认库。

12.tsconfig.json

如果一个目录下存在一个tsconfig.json文件,那么它意味着这个目录是TypeScript项目的根目录。 tsconfig.json文件中指定了用来编译这个项目的根文件和编译选项。在命令行上指定的编译选项会覆盖在tsconfig.json文件里的相应选项

可以使用 tsc --project 目录来指定一个包含tsconfig.json文件的目录

compilerOptions
这里主要用于指定编译的模块模式,以及一些编译报错的规则。若该选项被忽略,则会使用默认值

{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "removeComments": true,
        "preserveConstEnums": true,
        "sourceMap": true
    }
}

files
指定一个包含相对或绝对文件路径的列表。

{
    "files": [
        "core.ts",
        "sys.ts",
        "types.ts",
        "scanner.ts",
        "parser.ts",
        "utilities.ts",
        "binder.ts",
        "checker.ts",
        "emitter.ts",
        "program.ts",
        "commandLineParser.ts",
        "tsc.ts",
        "diagnosticInformationMap.generated.ts"
    ]
}

include/exclude
指定一个文件glob匹配模式列表。 支持的glob通配符有:

  • *匹配0或多个字符(不包括目录分隔符)
  • ? 匹配一个任意字符(不包括目录分隔符)
  • **/ 递归匹配任意子目录

优先级:file > exclude > include

    "include": [
        "src/**/*"
    ],
    "exclude": [
        "node_modules",
        "**/*.spec.ts"
    ]
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值