上手TypeScript,从入门到放弃

304 阅读4分钟

搭建环境

使用npm,全局安装TypeScript

npm install -g typescript

// 手动编译
tsc xxx.ts

// vscode 自动编译
1). 生成配置文件tsconfig.json
    tsc --init
2). 修改tsconfig.json配置
    "outDir": "./js",
    "strict": false,    
3). 启动监视任务: 
    终端 -> 运行任务 -> 监视tsconfig.json

基础类型

TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了枚举等类型。

字符串 

用 string表示文本数据类型。和JavaScript一样,可以使用双引号( ")或单引号(')表示字符串。

let uname = 'Ynag';
uname = 'PH';

支持模版字符串,它可以定义多行文本和内嵌表达式。

let uname = 'Ynag';
uname = 'PH';

let info = `我叫${uname}`;

数字

和JavaScript一样,TypeScript里的所有数字都是浮点数。 这些浮点数的类型是 number。 除了支持十进制十六进制字面量,TypeScript还支持ECMAScript 2015中引入的二进制八进制字面量。

let num1: number = 6; 
let num2: number = 0xf00d;  // 十六进制
let num3: number = 0b1010;  // 二进制
let num4: number = 0o744;   // 八进制

布尔值

简单的true/false值.

let isFlag: boolean = false;

 数组

有两种方式可以定义数组。 

第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组:

let numArr:number[] = [1, 2, 3];
let strArr:string[] = ['张三', '李四', '王五'];

第二种方式是使用数组泛型,Array<元素类型>

let numArr: Array<number> = [1, 2, 3];
let booleArr: Array<boolean> = [true, false];

元组 Tuple 

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

let tuple: [string, number, boolean, string];
tuple = ['张三', 20, true, '李四'];        √
tuple = ['张三', 20, false, '李四', 200];  ×

当访问一个已知索引的元素,会得到正确的类型:

console.log(tuple[0].substr(1)); // OK,字符串
console.log(tuple[1].substr(1)); // Error, 类型“number”上不存在属性“substr”

当访问一个不存在的元素,在2.7版本以后不被允许:

tuple[4] = 'world';  // 不能将类型"world"分配给类型“undefined”

枚举

enum类型是对JavaScript标准数据类型的一个补充,使用枚举类型可以为一组数值赋予友好的名字。

enum Gender {
    Man,
    Woman
}

let gender1: Gender = Gender.Man;
let gender2: Gender = Gender.Woman;

console.log(gender1, gender2);  // 0, 1

默认情况下,从0开始为元素编号。 你也可以手动的指定成员的数值。

enum Gender {
    Man = 3,
    Woman = 6
}

let gender1: Gender = Gender.Man;
let gender2: Gender = Gender.Woman;

console.log(gender1, gender2);  // 3, 6

你也可以由枚举的值得到它的名字。

let gender3: string = Gender[6];
console.log(gender3); // Woman

Any

为不清楚类型的变量指定一个类型,这些值可能来自于动态的内容,我们不希望类型检查器对这些值进行检查。

let val: any;
val = '张三';
val = 10;
val = true;

let arr:any[];
arr = ['张三', 10, true];
arr[3] = '李四';

console.log(arr); // ['张三', 10, true, '李四']

Void 

void类型像是与any类型相反,它表示没有任何类型。

let str:void =  10;        // Error, 不能将类型“number”分配给类型“void”
let str1:void = null;      // Error, 不能将类型“null”分配给类型“void”
let str2:void = undefined; // OK

当一个函数没有返回值时,通常其返回值类型是void。

function logMsg(): void {
    console.log('hello world!');
}

logMsg();

Null 和 Undefined 

TypeScript里,undefinednull两者各自有自己的类型分别叫做undefinednull

let u: undefined = undefined;
let n: null = null;

默认情况下nullundefined是所有类型的子类型。 就是说你可以把 nullundefined赋值给

一切。

但在严格类型模式中,nullundefined值不再属于任何类型的值,仅仅属于它们自己类型和any类型的值(例外:undefined也能赋值给void)。

// Error 
let u1: undefined = null;
let n1: null = undefined;
let u2:string = undefined;
let n2:number = null;

// OK~
let u3: string | undefined = undefined;
let n3: number | null = null;
let u4: boolean | null | undefined;
u4 = true;
u4 = null;
u4 = undefined;

在严格空检查模式中,编译器要求未包含undefined类型的局部变量在使用之前必须先赋值。

let x: number;
let y: number | null;
let z: number | undefined;
x;  // 错误,使用前未赋值
y;  // 错误,使用前未赋值
z;  // 正确
x = 1;
y = null;
x;  // 正确
y;  // 正确

Never

never类型表示的是那些永不存在的值的类型。抛出异常的函数,不会有返回值的表达式,箭头函数表达式的返回值类型。

// 函数返回never必须无法执行到终点
function error(message: string): never {
    throw new Error(message);
}
function infiniteLoop(): never {
    while (true) {
    }
}

// 推断返回类型是never
function fail() {
    return error("Something failed");
}

“never”仅表示类型,不能作为值使用。

Object

object表示非基本类型的类型,也就是除numberstringbooleansymbolnullundefined之外的类型。

let obj:object = {
    name: '张三',
    age: 18
}
obj = {
    name: '李四',
    age: 20
}

declare function create(o: object | null): void;
create({
    name: '张三'
})

类型断言 

了解某个值的详细信息,比它现有类型更确切的类型。原始any类型,告诉编译器是string类型。类型断言有两种形式。 其一是“尖括号”语法:

let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

另一个为as语法:(建议√)

let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

只是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。

接口

对值所具有的结构进行类型检查.

interface Person {
    name: string,
    age: number,
    job: string
}
let person: Person = {
    name: '张三',
    age: 20,
    job: '程序猿'
}
function logPerson(person: Person):void {
    console.log(`我叫${person.name}, 今年${person.age}了, 我的工作是${person.job}`);
}
logPerson(person);

可选属性,接口里的属性不全都是必需的。可以对可能存在的属性进行预定义,可以捕获引用了不存在的属性时的错误。

interface SquareConfig {
    color?: string;
    width?: number;
}
interface Square {
    color: string,
    area: number
}

function createSquare(config: SquareConfig): Square {
    let newSquare: Square = {color: "white", area: 100};
    if (config.color) {
        newSquare.color = config.color;
    }
    if (config.width) {
        newSquare.area = config.width * config.width;
    }
    return newSquare;
}

let mySquare = createSquare({color: "black"});

只读属性, 一些对象属性只能在对象刚刚创建的时候修改其值

interface Point {
    readonly x: number;
    readonly y: number;
}

let p: Point = { x: 10, y: 20 };
p.x = 15; // Error

TypeScript具有ReadonlyArray<T>类型,它与Array<T>相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:

let arr:number[] = [1, 2, 3, 4];
arr.push(5);
console.log(arr); // [1, 2, 3, 4, 5]
let ra:ReadonlyArray<number> = [9, 8, 7, 6];
console.log(ra); // [9, 8, 7, 6]

ra[0] = 10;     // Error
ra.push(5);     // Error
ra.length = 10; // Error
ra = arr;       // OK
arr = ra;       // Error, 把整个ReadonlyArray赋值到一个普通数组也是不可以的

把整个ReadonlyArray赋值到一个普通数组也是不可以的,但是你可以用类型断言重写。

a = ro as number[];

readonly vs const

最简单判断该用readonly还是const的方法是看要把它做为变量使用还是做为一个属性。 

做为变量使用的话用 const,若做为属性则使用readonly。 

额外的属性检查

对象字面量会被特殊对待而且会经过 额外属性检查,当将它们赋值给变量或作为参数传递的时候。 如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误。

interface SquareConfig {
    color?: string;
    width?: number;
}
function createSquare(config: SquareConfig):void {
    // ......
}
// Error, 对象文字只能指定已知的属性,“colour”中不存在类型“SquareConfig”
let mySquare = createSquare({ colour: "red", width: 100 });

解决方案,使用类型断言:

let mySquare = createSquare({ opacity: 0.5, width: 100} as SquareConfig);

最佳方案,添加一个字符串索引签名,表示有任意数量的属性(不是width、color)。

interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;
}

不建议使用,跳过这些检查的方式,将这个对象赋值给一个另一个变量。

interface SquareConfig {
    color?: string;
    width?: number;
}
function createSquare(config: SquareConfig):void {
    // ......
}
let squareOptions = {
    colour: "red",
    width: 100
};
// squareOptions不会经过额外属性检查,所以编译器不会报错
let mySquare = createSquare(squareOptions);

函数类型

像是一个只有参数列表返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。

// 正常函数书写格式
function fun(first: number, last: number): boolean {
    return first > last;
}

//  接口描述的函数类型
interface Compare {
    (first: number, last: number): boolean;
}
let size: Compare = function (first: number, last: number): boolean {
    return first > last;
}
// 精简写法
let myCompare: Compare = function(a, b) {
    return a > b;
}

console.log(size(10, 20));
console.log(myCompare(20, 10));

类类型

TypeScript也能够用类类型来明确的强制一个类去符合某种契约。

interface ClockTime {
    currentTime: Date,
    setTime?(d: Date):void
}

class Clock implements ClockTime {
    currentTime: Date
    constructor(h:number, m:number) {
        console.log(h, m);
    }
    setTime(d:Date) {
        console.log(d);
    }
}

let d = new Date();
let clock = new Clock(d.getHours(), d.getMinutes());
clock.setTime(d);

继承接口

从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。

interface Animal {
    name: string,
    age: number
}
interface Cat extends Animal {
    color: string,
    [attr:string]: any // 添加一个字符串索引签名
}
let cat = {} as Cat;
cat.name = '七月';
cat.age = 2;
cat.color = 'orange';
cat.breed = '橘猫'; // 类型“Cat”上不存在属性“breed”
console.log(cat);

一个接口可以继承多个接口,创建出多个接口的合成接口。

interface Shape {
    color: string
}
interface Circle {
    width: number
}
interface Square extends Shape, Circle {
    border: number
}

let square = {} as Square;
square.color = 'red';
square.width = 10;
square.border = 2;

类的基本使用

class Cat {
    catName: string
    constructor(catName: string) {
        this.catName = catName;
    }
    say ():void {
        console.log(`大家好,我的名字叫${this.catName}.`);
    }
}

let cat = new Cat('七月');
cat.say();

类的继承

class Animal {
    uname: string
    constructor (uname: string) {
        this.uname = uname;
    }
    logName ():void {
        console.log('大家好,我是' + this.uname);
    }
}
class Dog extends Animal {
    breed: string
    uname: string
    constructor (uname: string, breed: string) {
        super(uname);  // 派生类的构造函数必须包含 "super" 调用
        this.breed = breed;
    }
    breeds ():void {
        console.log('我的品种是' + this.breed);
    }
}

let dog = new Dog('七月的风', '哈巴狗');
dog.logName();
dog.breeds();

更复杂的例子,多重继承

class Animal {
    uname: string
    constructor (uname: string) { 
       this.uname = uname;
    }
    move (distance: number) {
        console.log(`${this.uname}走动了${distance}m.`);
    }
}
class Dog extends Animal {
    uname: string
    constructor (uname: string) { 
       super(uname);  // 派生类的构造函数必须包含 "super" 调用
    }
    move (distance: number) {
        console.log(`我是犬科物种......`);
        super.move(distance);
    }
}
class Cat extends Animal {
    uname: string
    constructor (uname: string) {
        super(uname);  // 派生类的构造函数必须包含 "super" 调用
    }
    move (distance: number) {
        console.log(`我是猫科物种...`);
        super.move(distance);
    }
}

let dog = new Dog('七月');
dog.move(100);
let cat = new Cat('八月');
cat.move(190);

公共,私有与受保护的修饰符

成员都默认为 public

class Animal {
    public uname: string
    public constructor (uname: string) { 
       this.uname = uname;
    }
    public move (distance: number) {
        console.log(`${this.uname}走动了${distance}m.`);
    }
}

当成员被标记成 private时,它就不能在声明它的类的外部访问。

class Animal {
    private uname: string
    constructor (uname: string) { 
       this.uname = uname;
    }
}
let animal = new Animal('猫');
animal.name; // Error, ‘uname’是私有的

protected成员在派生类中仍然可以访问。实例化后,不允许被使用。

readonly关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。

函数的使用

函数的基本使用

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

max(10);        // Error, 应该有2个参数
max(10, 20);    // OK
max(10, 20, 30);// Error, 应该有2个参数

 可选参数和默认参数

// y是可选参数, z是默认参数
function max(x:number, y?:number, z = 5):void {
    console.log(x + y + z);
}

max(10);  // NAN                   
max(10, 20);  // 35
max(10, 20, 30); // 60
max(10, undefined, 30); // NAN
max(10, null, 30); // 40

剩余参数 

function max(x:number, ...num:number[]):void {
    console.log(x, num);
}

max(10);  // 10, []
max(10, 1, 2, 3, 4); // 10, [1, 2, 3, 4]