ts小书

1,285 阅读16分钟

TypeScript

定义

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

优势

1.TypeScript 增加了代码的可读性和可维护性

  • 类型系统实际上是最好的文档,大部分的函数看看类型的定义就可以知道如何使用了。
  • 使用类型注解能够在变量声明的时候确定变量存储的值的类型,用来约束变量或参数值 的类型,这样在编码阶段就可以检查出可能出现的问题,避免把错误带到执行期间。
  • 增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、重构等。

2.TypeScript 非常包容

  • TypeScript 是 JavaScript 的超集,.js 文件可以直接重命名为 .ts 即可。
  • 即使不显式的定义类型,也能够自动做出类型推论。
  • 可以定义从简单到复杂的几乎一切类型。
  • 即使 TypeScript 编译报错,也可以生成 JavaScript 文件。
  • 兼容第三方库,即使第三方库不是用 TypeScript 写的,也可以编写单独的类型文件供 TypeScript 读取。

安装

npm install -g typescript 

执行以上命令会在全局环境下安装 tsc 命令,之后便可使用“tsc filename.ts”编译一 个 TypeScript 文件。

类型系统

基础数据类型

  • 布尔值
js语法 let isBoolean = true
ts语法 let isBoolean:boolean = true
  • 数字
js语法 let num = 1 
ts语法 let num:number = 1
  • 字符串
js语法 let str = ""
ts语法 let str:string = ""
  • 数组
js语法 let arr = [1,2,3]
ts语法(ts中定义数组的方式有两种)
方式一: let arr:number[] = [1,2,3]
方式二: let arr:Array<number> = [1,2,3]
  • 元组
    元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
js语法 let tuple = ['hello',10]
ts语法 let tuple:[string,number] = ['hello',10]
  • 空值
ts语法 void
  • null
js语法 let n = null
ts语法 let n:null = null
  • undefined
js语法 let u = undefined
ts语法 let u:undefined = undefined

画重点哦~~~

  • 声明一个 void 类型的变量没有什么用,一般是用作没有返回值的函数的声明 。
  • undefined 类型的变量只能被赋值为 undefined,null 类型的变量只能被赋值为 null。undefined 和 null 是所有类型的子类型。也就是说 undefined 和 null 可以赋值给其它 类型,但是当你指定了--strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们 各自。
  • string、number、boolean:基本类型;String、Number、Boolean 为对象类型;基本类型 可以赋值给对应包装对象,包装对象不可以赋值给对应基本类型。 ✅ let s: String = 'miaov'
    ❎ let s: string = new String('miaov')

任意值

任意值(Any)用来表示允许赋值为任意类型。如果是一个普通类型,在赋值过程中改 变类型是不被允许的:比如

let str:string = 'hello'
str = 7

此时,编译器会提示错误

但是,TypeScript允许我们对any类型的值进行任何操作,事先无需进行任何检查。
变量如果在声明的时候未指定其类型,则会被默认为any类型。(类型推论)
注意,使用此属性相当于跳过ts的类型检查,慎用!!!

类型推论

如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。

let str = 'string'
str = 7

此时,编译器会出现错误提示

变量str在定义的时候赋值为字符串“string”,所以该变量被自动推导为字符串类型,在赋值为其它类型的值就会报错。

const foo = {
    a:123,
    b:456
}
foo.a = 'hello'

此时,编译器会出现错误提示

foo 的类型被推断为 { a: number, b: number },所以再将字符串类型的值赋值给foo.a时会出现错误提示。

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

let str;
str = 'string'
str = 7

联合类型

联合类型表示取值可以为多种类型中的一种。联合类型使用 | 分隔每种类型

let unionType = string | number;
unionType = 'string'
unionType = 1

即变量unionType的类型可以是字符串类型也可以是数字类型,所以赋值为字符串或数字均不会报错,但是不可以赋值为这两者之外的类型。
访问联合类型的时候属性和方法时只能访问联合类型共有的属性和方法

let unionType:string | number;
unionType = 'string'
unionType = 1
console.log(unionType.length)
console.log(unionType.toString())

字符串存在length属性,但是数字不存在length属性,所以访问unionType上的length属性时报错了。但是两者皆有toString方法,所以可以访问unionType的toString方法。
联合类型的变量在赋值时,会根据赋值的类型自动推导出类型

let unionType:String | number;
unionType = 'string'
console.log(unionType.length)
unionType = 1
console.log(unionType.lenght)

第一次unionType被赋值为字符串,则该变量被推断为字符串类型,所以可访问该变量的length属性。第二次unionType被赋值为数字,则该变量被推断为数字类型,所以该变量上没有length属性。

数组的类型

在 TypeScript 中,数组类型有多种定义方式。

  • 「类型 + 方括号」表示法
let fibonacci:number[] = [1,1,2,3,5]
let f:(number | string)[] = [1,1,2,3,'5']
  • 泛型表示法 Array<类型>
let fibonacci:Array<number> = [1,1,2,3,5]
  • 接口表示法
interface NumberArray {
    [index:number]:number;
}
let fibonacci:NumberArray = [1,1,2,3,5];

接口NumberArray代表了一种键为number类型,值也为number类型的对象

对象的类型

在TypeScript中,使用接口interface来定义对象的类型(object也可以定义对象的类型,但是取对象的值的时候会报错,提示找不到)

接口

在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。 TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。

  • 确定属性
interface Person {
    name:string;
    age:number;
}

let tom:Person = {
    name:"tom",
    age:25
}

上面的例子中,我们声明了一个Person接口,然后定义了一个类型为Person的tom变量,因此tom的形状必须要跟接口Person一致,定义的变量的属性比接口少或者多都不行。

  • 可选属性
    如有接口中有不确定的属性,则接口可以采用可选属性,如下
interface Person {
    name:string;
    age?:number;
}

let tom:Person = {
    name:"Tom"
}

let lili:Person = {
    name:"lili",
    age:25
}

可选属性名字定义的后面加一个?符号,就代表变量中该字段存在或否都可以,但是仍然不允许添加未定义的属性。

  • 任意属性
    如果接口中的属性都是不确定的,则可以采用任意属性
interface Person {
    name:string;
    age?:number;
    [propName:string]:any;
}
let tom:Person = {
    name:'Tom',
    gerder:'male'
}

使用 [propName: string] 定义了任意属性取 string 类型的值。需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:上述例子中的确定属性name及可选属性age的类型就必须是任意属性的类型。

  • 只读属性
    有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性:
interface Person {
    readonly id:number;
    name:string;
    age?:number;
    [propName:string]:any;
}
let tom:Person = {
    id:89757,
    name:"Tom",
    gender:"male"
}

接口Person中的id前加了readonly标识符,则代表该属性为只读属性,则类型为Person的变量中的id只能在创建的时候赋值,后期不能再次进行赋值。

函数的类型

Js中的函数有两种形式,一种是有名字的函数,另一种是匿名函数。Ts中添加了类型定义如下:

//Named function
function add(x:number,y:number):number {
    return x + y
}

//Anonymous function
let myAdd = function(x:number,y:number):number {
    return x + y
}

我们可以给每个函数的参数添加类型后,再给函数的返回值添加类型,返回值的类型声明一般可以省略,ts会自动根据返回语句自动推断出函数返回值的类型。 上图中的匿名函数,只给右侧的匿名函数添加了类型定义,但是左侧的myAdd并没有添加类型定义,它是由ts根据右侧的函数自动推断出来的,完整的函数类型声明如下:

//Anonymous function
let myAdd:(num1:number,num2?:number) => number = function(x:number,y:number):number {
    return x + y
}

也可以使用接口来定义函数的形状

interface myFunc {
    (num1: number,num2: number): number
}
let myAdd:myFunc = function (x: number,y: number): number {
    return x + y
}

  • 可选参数
    上面的函数参数声明中,输入多余的参数或者少输入参数都是不被允许的,也就是说传递给一个函数的参数个数必须与函数期望的参数个数一致。
function buildName(firstName: string,lastName: string){
    return firstName + " " + lastName;
}
let result1 = buildName("Bob");
Let result2 = buildName("Bob","Adams","Sr.");
let result3 = buildName("Bob","Adams");

若有不确定的参数,这个时候可以使用可选参数,即在参数名后加上“?”符号。

function buildName(firstName: string,lastName?: string){
    return firstName + " " + lastName
}
let result1 = buildName("Bob");
let result2 = buildName("Bob","Adams")

可选参数必须跟在必选参数的后面。
给参数添加了默认值的话,该参数会被认定为可选参数,区别是该参数可以放在必选参数的前面。

  • 剩余参数
    当一个函数的参数个数不定时,这个时候可以使用剩余参数。剩余参数可以视作个数不限的可选参数,可以一个也没有,也可以有任意多个。
function buildName(firstName: string,...restName: string[]){
    return firstName + " " + restName.join("")
}

let result1 = buildName("Bob");
let result1 = buildName("Bob","Adams");
let result1 = buildName("Bob","Adams","lili","tom","lucy");
  • 重载
    重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。比如:
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("")
    }
}

上图中的例子是想实现当函数参数传入的是字符串时,输出该字符串的反转;当函数参数传入的是数字时,输出该数字的反转。函数返回值定义的也是字符串和数字的联合类型,这样就会造成表达的不是很明确,假如我输入的时数字,那么返回值应该是字符串类型呢还是数字类型呢?这种时候可以采用函数重载,对该函数做出明确的类型描述。

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

前两个是函数的重载:一个接受数字类型,一个接收字符串类型。最后一个是函数的实现。编译器在进行类型检查时,会从最前面的重载开始检查,如果匹配就使用,因此在定义重载的时候一定要把最精确的定义放在最前面。

泛型

软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。我们可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

泛型变量

当已知参数类型及返回值类型为确定的,比如数值的时候:

function identity(arg: number): number {
    return arg;
}

当参数类型及返回值类型未知的时候,我们可以使用any,但是使用any类型会导致这个函数可以接收任何类型的arg参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的。如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。

function identity(arg: any): any {
    return arg;
}

若想返回值的类型与传入参数的类型是相同的,这个时候我们可以使用泛型

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

我们在函数名后面添加了类型变量T(类型变量是一种特殊的变量,只表示类型而不是值),其中T代表任意可以输入的类型,后续便可使用T来定义参数及返回值的类型。函数在调用的时候可以指定具体的类型:

function identity<T>(arg: T): T {
    return arg
}
identity(string)('hello')
identity(number)(1)

或者直接传入值,利用类型推论,即编译器会根据传入的参数自动地帮助我们确定T的类型:

function identity<T>(arg: T): T {
    return arg
}
identity('hello')
identity(1)

多个类型参数时:

function swap<T,U>(tuple:[T,U]):[U,T]{
    return [tuple[1],tuple[0]]
}
swap([7,"seven"])

泛型参数的默认参数

在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。

function identity<T = string>(arg: T): T {
    return arg
}
identity("hello")
identity("0")

泛型约束

在函数内部使用泛型变量时,由于事先不知道变量的具体类型,所以不能随意使用变量的属性和方法。

function identity<T = string>(arg: T): T {
    return arg.length;
}

这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束:

interface lengthwise {
    length:number
}
function identity<T extends lengthwise>(arg: T): T {
    console.log(arg.length)
    return arg;
}

上例中,我们使用了 extends 约束了泛型 T 必须符合接口 Lengthwise 的形状,也就是必须包含 length 属性。 多个参数之间也可以互相约束

function copyFields<T extends U, U>(target: T,source: U): T {
    for(let id in source) {
        target[id] = (<T>source)[id]
    }
    return target
}
let x = {a: 1,b: 2,c: 3,d: 4}
copyFields(x, {b: 10,d: 20})

上例中,我们使用了两个类型参数,其中要求 T 继承 U,这样就保证了 U 上不会出现 T 中不存在的字段。

声明文件

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

  • 什么是声明语句?
    当我们想要使用JQuery库时,我们可以通过script标签将juery文件引入页面,便可以用 $(“#foo”)或者jQuery('#foo')来查找文件。但是如果在ts中时,编译器并不知道$()或者jQuery是什么?这个时候就需要使用declare var来声明
declare var jQuery: (selector: string) => any;
jQuery("#foo")

但上例中并没有真的定义一个变量,只是定义了全局变量jQuery的类型,仅仅会用于编译时的类型检查,编译后会被删除。

  • 什么是声明文件?
    把声明语句提取出来放到一个单独的文件里,这就是声明文件,比如将上面的jQuery声明单独放到文件jQuery.d.ts中,这就是一个声明文件。声明文件必须是以d.ts为后缀。有很多第三方库的声明文件我们一般不需要自己定义,可以直接引用@types包中的相应声明文件。 以jQuery为例:
npm install @types/jquery --save-dev

但是当一个第三方库没有提供声明文件时,我们就需要自己书写声明文件。
详情可见文档

装饰器

装饰器是对类、函数、属性之类的一种装饰,可以针对其添加一些额外的行为。通俗的理解可以认为就是在原有代码外层包装了一层处理逻辑。 可以简单地理解为是非侵入式的行为修改。装饰器使用 @expression 这种形式,expression 求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
定义装饰器的时候,参数 最多有三个,target、name、descriptor。
Decorators 的本质是利用了ES5 的 Object.defineProperty 属性,这三个参数其实是和 Object.defineProperty 参数一致的,因此不能更改。

启用装饰器

tsconfig.json 的 compilerOptions.experimentalDecorators 设置为 true。

类装饰器

function isAnimal(target: any){
    target.isAnimal = true
}
@isAnimal
class Cat {
    // ...
}
console.log((Cat as any).isAnimal);
@decorator
class A {}
//等同于
class A {}
A = decorator(A) || A

详情可见文档

typeScript+Vue

与新建一个vue项目的区别

新建项目时,配置中选择TS。新建完成后的文件夹中较之前新增了shims-tsx.d.ts、shims-vue.d.ts、tslint.json 、tsconfig.json。
shims-tsx.d.ts 文件,这个文件主要是方便你使用在 ts 中使用 jsx 语法的,如果不使用 jsx 语法,可以无视这个。
由于 TypeScript 默认并不支持 *.vue 后缀的文件,所以在 vue项目中引入的时候需要创建一个 shims-vue.d.ts 文件,意思是告诉 TypeScript *.vue 后缀的文件可以交给 vue 模块来处理,而在代码中导入 *.vue 文件的时候,需要写上 .vue 后缀。原因还是因为 TypeScript 默认只识别 *.ts 文件,不识别 *.vue 文件。
TSLint 与 ESLint 类似,不过除了能检查常规的 js 代码风格之外,TSLint 还能够通过 TypeScript 的语法解析,利用类型系统做一些 ESLint 做不到的检查。
tsconfig.json文件中指定了用来编译这个项目的根文件和编译选项。
编译选项参考官方

component的两种风格

Vue.extend 构造器

class-style component syntax

类风格的组件

语法变更可以分成 3 类:

  • 直接写在类下
    data, methods, render, errorCaptured;
    computed,类访问器写法 get;
    lifecycle hooks: beforeCreate, created, beforeMount, mounted, beforeDestroy, destroyed, beforeUpdate, updated, activated
  • 需要装饰器:props, watch
  • 除了上述指明的属性外其余均需要放到 Component 装饰器的 options 内,
    如:name, components, filters, directives 等
    router hooks: beforeRouteEnter,beforeRouteUpdate, beforeRouteLeave

Vuex

可参考以下两种插件

Vuex-class

vuex-module-decorators

戳详情

文章都是借鉴以下几位大佬的,参考地址:
1、ts.xcatliu.com/basics/prim…
2、zh-rocco.github.io/vue-typescr…
3、官方文档:www.typescriptlang.org/