Typescript的一些学习记录

1,769 阅读7分钟

什么是TypeScript

TypeScript是JavaScript的一个超集,主要提供了类型系统和对ES6的支持。

TS的优势

  1. 增加了代码的可读性和可维护性 类型系统实际上是最好的文档,大部分的函数看看类型的定义就可以知道如何使用了 可以在编译阶段就发现大部分错误 增强了编辑器和IDE的功能,包括代码补全,接口提示,跳转到定义,重构等

  2. ts非常包容 即使不显式的定义类型,也能够自动做出类型推导; 可以定义从简单到复杂的几乎一切类型; 即使ts编译报错,也可以生成js文件; 兼容第三方库,即使第三方库不是用ts写的,也可以编写单独的类型文件供ts读取

  3. 活跃的社区

TS的缺点

  1. 有一定的学习成本,需要理解接口(Interfaces), 泛型(Generics), 类(Classes), 枚举类型(Enums)等前端不熟悉的概念
  2. 短期增加开发成本,但可以减少后期维护成本
  3. 集成到构建流程需要一定的工作量
  4. 可能和一些库结合的不是很完美

安装TS

npm i typescript -g 以上命令会在全局环境下安装tsc命令,安装完成后,我们就可以在任何地方执行tsc命令了。 tsc hello.ts

小demo

// hello.ts

function sayHello(person: string) {
    return 'Hello, ' + person;
}

let user = 'Tom';
console.log(sayHello(user));

执行tsc hello.ts会:

//hello.js

function sayHello(person) {
    return 'Hello, ' + person;
}
var user = 'Tom';
console.log(sayHello(user));

ts只会进行静态检查,如果发现有错误,编译的时候就会报错

如果在报错的时候终止js文件的生成,可以在tsconfig.json中配置noEmitOnError就可以了。

原始数据类型

布尔值

let isDone: boolean = false

数值

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的二进制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八进制表示法

字符串

let myName: string = 'Tom'

let sentence: string = `hello, my name is ${myName}`

空值

JS中没有空值(void)的概念, 在ts中,可以用void表示没有任何返回值的函数


function alertName(): void {
    alert('my name is Tom')
}

声明一个void类型的变量没有什么用,因为只能讲它赋值为undefinednull

let unusable: void = undefined

Null和Undefined

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

与void的区别是, undefined和null是所有类型的子类型。也就是说undefined类型的变量,可以赋值给number类型的变量:

let num: number = undefined
//这样不会报错

let u: undefined
let num: number = u
//这样也不会报错

let u: void
let num: number = u
//Type 'void' is not assignable to type 'number'.

任意值

任意值(Any)用来表示允许赋值为任意类型

如果是一个普通类型,在赋值过程中改变类型是不被允许的,但如果是any类型,则被允许赋值为任意类型:


let str: string = 'sens'
str = 5
// Type 'number' is not assignable to type 'string'

let str: any = 'sens'
str = 5
// ok

在任意值上访问任何属性都是允许的,也允许调用任何方法:


let anyThing: any = 'hello';
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);

let anyThing: any = 'Tom';
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');

可以认为, 声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值

未声明类型的变量

变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型。

类型推论

ts会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。

如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成any类型而完全不被类型检查

联合类型

联合类型表示取值可以为多种类型中的一种


let str: string | number

联合类型使用|分隔每个类型

访问联合类型的属性和方法

当ts不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:

function getLength(something: string | number): number {
    return something.length;
}
// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
//   Property 'length' does not exist on type 'number'

对象的类型 -- 接口

在ts中,使用接口(interfaces)来定义对象的类型

什么是接口

在面向对象的语言中,接口是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类去实现。

interface Person {
    name: strin;
    age: number;
}

let tom: Person = {
    name: 'Tom',
    age: 23
}

接口一般首字母大写。

定义的变量比接口少了一些属性是不被允许的,多一些属性也是不允许的。赋值的时候, 变量的形状和接口的形状必须保持一致

可选属性

有时我们希望不要完全匹配一个形状,那么可以用可选属性:

interface Person {
    name: string;
    age?: number;
}

任意属性

有时我们希望一个接口允许有任意的属性:

interface Person {
    name: string;
    age?: number;
    [propName: string]: any
}
// 使用[propName: string]定义了任意属性取string类型的值

需要注意,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:

interface Person {
    name: string;
    age?: number;
    [propName: string]: string;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};
//报错,因为age是number类型不是strin的子类型

只读属性

interface Person {
    readonly: id: number;
    name: string;
    age: number;
    [propName: string]: any
}

注意:只读的约束在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候。

数组的类型

在ts中,数组类型有多种定义方式,比较灵活。

let fib: number[] = [1,2,3]
//数组的项中不允许出现非number类型
  1. 数组泛型
let fib: Array<number> = [1,2,3]
  1. 用接口表示数组
interface NumberArray {
    [index: number]: number
}
//上面的定义表示: 只要索引的类型是数字时,那么值得类型必须是数字
let fib: NumberArray = [1,2,3]
  1. 类数组

function sum() {
    let args: number[] = arguments
} 
//报错,arguments是一个类数组,不能用普通的数组定义,而是使用接口:

function sum() {
    let args: {
        [index: number]: number;
        length: number;
        callee: Function;
    } = arguments;
}

事实上,常用的类数组都有自己的接口定义, 如IArguments, NodeList, HTMLCollection等:

function sum() {
    let args: IArguments = arguments
}

any在数组中的应用

let list: any[] = ['dd', 2, {ss: 'cc'}]

函数的类型

//函数声明
function sum(x: number, y: number): number {
    return x + y
}

//函数表达式

let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};
//注意: 不要混淆了ts中的箭头和es6中的箭头
//在ts中箭头表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。

注意: 输入多余的参数是不被允许的。

用接口定义函数的形状

interface Search {
    (source: string, sub: string): boolean
}

let mySearch: Search

mySearch = function(source: string, sub: string) {
    return boolean
}

可选参数

前面强调输入多余(少于)参数,是不允许的,但可以用?表示可选参数:

function buildName(firstName: string, lastName?: string) {
    if (lastName) {
        return firstName + ' ' + lastName;
    } else {
        return firstName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

注意: 可选参数必须在必需参数后面,换句话说,可选参数后面不允许再出现必须参数了。

参数默认值

ts会将添加了默认值的参数识别为可选参数。

剩余参数

es6中,可以用...的方式获取函数中的剩余参数:

function push(array: any[], ...items: any[]){}

重载

可以使用重载定义多个reverse的函数类型:

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。

注意: ts会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。

类型断言

两种写法:

<类型>值

值 as 类型
//react只能使用第二种

function getLength(something: string | number): number {
    if ((something as string).length) {
        return something as string).length;
    } else {
        return something.toString().length;
    }
}

类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的。

声明文件

当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码不全,接口提示等功能。

声明文件必须以.d.ts为后缀

当一个第三方库没有提供声明文件时,我们就需要自己书写声明文件了。

库的主要使用场景:

  1. 全局变量: 通过