一、前言

学完一个东西,总要总结一下才能巩固,好记性不如烂笔头。本文想对TypeScript进行详细的基础知识总结,若有错误之处还请指正。另外本文中的示例可直接在 playground 上运行。

使用的TypeScript版本: v4.8.4

二、优缺点

1、优点

提前发现错误:在编写代码的时候就能发现大部分的错误,避免代码到运行出现bug。更愉快的写代码:结合编辑器(vscode真是好东西)的语法提示功能、智能补全功能、类型定义等。可读性和可维护性:能够及时的检测错误,程序员更容易写出更健壮、更清晰的代码,在大型项目中不同的程序员维护更加简单。

2、缺点

增加学习成本

三、类型

1️⃣ 基础类型

boolean string number enum undefined null Array tuple any unknown void never

1、boolean、string、number

const amIRich: boolean = true

const myName: string = "Tony"

const myAge: number = 18

console.log(typeof amIRich)

console.log(typeof myName)

console.log(typeof myAge)

2、enum

简单使用 第一个值默认是0,剩下的会按顺序增加,类似数组的下标。

enum Animal {

DOG,

CAT,

MOUSE

}

const animal: Animal = Animal.DOG

console.log(animal) // 打印 0

设置初始值

// number

enum Animal {

DOG = 1,

CAT, // 自动为2

MOUSE // 自动为3

}

const cat: Animal = Animal.CAT

console.log(cat) // 打印 2

// string 必须手动设置 不能自动推断

enum City {

SH = "shanghai",

BJ = "beijing",

SZ = "shenzhen"

}

const shanghai: City = City.SH

console.log(shanghai) // 打印 shanghai

const 修饰的常量枚举 用const 修饰的常量枚举在ts转换成js的时候,不会保留枚举的特性。

// const 修饰的enum

const enum City {

SH = "shanghai",

BJ = "beijing",

SZ = "shenzhen"

}

const shanghai: City = City.SH

console.log(shanghai) // 打印 shanghai

Playground 中翻译的js如下:

"use strict";

const shanghai = "shanghai" /* City.SH */;

console.log(shanghai); // 打印 shanghai

3、undefined、null

默认情况下 可以把 undefined 和 null 赋值给其他类型

// 未开启 "strictNullChecks":true

let u: undefined = undefined

let n: null = null

// 可以复制给其他类型

const myName: string = u

const willRich: boolean = n

// 还可以相互赋值

u = n

配置文件tsconfig.json中 配置 “strictNullChecks”:true

// 开启了 "strictNullChecks":true

let u: undefined = undefined

let n: null = null

// const name: string = u // 会报错

// u = n // 不能相互赋值

// undefined 可以赋值给 void

// null 不能复制给void

const a: void = u

// const b: void = n // 会报错

4、Array、tuple

元组(tuple) 规定了已知长度和类型的数组,只能按定义的长度和对应的类型来赋值。

// 普通的数组

const arr: string[] = ['dog', 'cat', 'mouse']

//等价于

const arr1: Array = ['dog', 'cat', 'mouse']

// tuple元组是 更加严格的数组

const zhangsan: [string, number, string] = ['张三', 18, '男']

5、any、unknown

any: 可以说是ts开的“后台”,可以跳过类型检查,所以任何类型都可以复制给any。

// any -> 任何类型赋值给any

const MyName: any = '法外狂徒张三'

const myMoney: any = 100000000

const isAnimal: any = true

// any -> any赋值给其他任何类型

const newName: string = MyName

const dog: boolean = isAnimal

unknown: 和any一样,所有类型都可以赋值给unknown。区别在于unknown只能赋值给unknown和any。

// any -> 任何类型赋值给any

const MyName: unknown = '法外狂徒张三'

const myMoney: unknown = 100000000

const isAnimal: unknown = true

// any -> any赋值给其他类型

// const newName: string = MyName // 报错

const money: unknown = 100000000 // 正确

const dog: unknown = isAnimal // 正确

6、void、never

void常用在函数上,一般函数有返回值,有可能是string、number、boolean等等,当函数没有返回值的时候,则是void类型,表明函数没有返回值。

function fn(): void {

console.log('今天你内卷了吗?')

console.log('没有返回值')

}

fn()

never表示永远也不会执行完的函数。比如说在函数内部有死循环,这个函数永远也不会执行结束。或者函数中有抛出异常,执行的时候被异常中断了,这个函数也就没有执行到底。

// 死循环

function fn(): never {

while(true){}

}

// 抛出异常

function errFn(): never {

throw new Error()

}

2️⃣ 对象类型

函数 对象 数组 类

1、函数

函数是用的非常多的类型了。

普通使用

const fn: () => void = () => {

console.log('中国红')

}

// () => void 表示 fn的类型是一个没有参数 返回值是void(就是没有返回值)的函数

// () => { console.log('中国红') } 是fn的值

默认参数

function fn(age: number = 18):void {

console.log('您的年龄是: ' + age)

}

fn()

可选参数

function sum(a: number, b: number = 1, c?: number) {

if(c) {

return a + b + c

}

return a + b

}

console.log(sum(1,2)) // 3

console.log(sum(1,2,3))// 6

剩余参数

必须是一个数组类型。

function sum(...numbers: number[]) {

let res = 0

for(let i = 0; i < numbers.length; i++) {

res += numbers[i]

}

return res

}

console.log(sum(1,2)) // 3

console.log(sum(1,2,3))// 6

2、对象、数组、类

对象

一般用 接口:interface 来实现类似的功能,所以此处不在深入。

let a: object = {}

let b: object = { name: '张三' }

let c: {}

c = { name: '张三' }

console.log(c)

数组

// 普通的数组

const arr: string[] = ['dog', 'cat', 'mouse']

//等价于

const arr1: Array = ['dog', 'cat', 'mouse']

简单的了解一下,后面做详细的介绍。

class People {

name: string

age: number

constructor(name:string, age: number) {

this.name = name

this.age = age

}

speak() {

console.log('我的名字: ' + this.name)

console.log('我的年龄: ' + this.age)

}

}

const zhangsan = new People('张三', 18)

zhangsan.speak()

3️⃣ 接口 interface

interface 用的也非常广泛,灵活性也非常强。也常用于对「对象的形状(Shape)」进行描述。一般首字母大写。定义接口类型的变量需要实现里面全部的普通的属性或者方法。

简单的例子

interface Person {

name: string

age: number

speak: () => void // 定义函数类型

}

const lisi: Person = {

name: '李四',

age: 18,

speak() {

console.log('我会说话')

}

}

可选属性、只读属性 可选属性允许不需要必须实现接口里面的属性或者方法,也就是说该属性或者方法可以实现也可实现。用 *?*表示。 只读属性就是只允许读取,不允许修改的属性。

interface Person {

name?: string

readonly age: number

}

const lisi: Person = {

// name: '李四', // 可选 可以定义也可以不定义

age: 18

}

// readonly 修饰 只读

// lisi.age = 19 // 报错

鸭式辨型法

就是说只要有鸭子的特征他就是鸭子,只要传入的对象包含了接口的属性或者方法(鸭子的特征)那么可以认定传入的参数实现了这个接口,可以绕开额外的属性检查。

interface nameValue {

name: string;

}

function getNmae(nameObj: nameValue) {

console.log(nameObj.name);

}

let lisi = { age: 18, name: "我是李四" };

getNmae(lisi); // 通过

// getNmae({ age: 18, name: "我是李四" }); // 报错

4️⃣ 类型操作

1、联合类型

某个变量可能有两个或者两个以上的类型,用 | 来表示。

let a: string | number = 1

a = 2 // 正确

a = 'lisi' // 正确

2、交叉类型

变量的类型,是两个类型的结合,相同key中的类型要一样。用 & 来表示。

interface a {

name: number

age: number

}

interface b {

name: string

city: string

}

// c 包含 a 和 b 属性

// 如果a和b中的name属性类型不一样的话,则不能使用 &

let c: (a & b) = {

name: '李四',

age: 18,

city: '北京'

}

3、类型推断

没有明确指定类型的时候,ts会尝试推断其类型。推断不出来的时候会赋予any类型。

const myName = "lisi" // 推断出 myName 是 string类型

4、类型断言

有时候自己更加清楚知道变量的类型,所以可以手动指定类型,使用 as 来指定类型.

// 方式一 使用 <类型>值

const Myname = "abcdefg"

const count1 = (Myname as string).length

5、类型别名

使用type来表示,相当于给类型重新起一个名字, 可以用在基础的类型如 string 、number、boolean和interface定义的类型等等。

interface a {

name: number

say: () => void

}

type str = string // str 是 string类型的另一个名字

type num = number

type b = a // b 是 a类型的别名

6、类型守卫

当传进来的值的类型不止一种的时候,就需要对值进一步的判断,确保值的类型在可控的范围内。

in 关键字

可以判断 某个 key 在不在其中。

interface a {

name: number

age: number

}

interface b {

name: string

city: string

}

function fn(param: a | b) {

if('age' in param)

console.log(param.age)

if('city' in param)

console.log(param.city)

}

typeof 关键字

和js中的typeof类似,可以判断 bool、string、number等等的类型

function fn(x: number|string, y: number|string):string|number {

if(typeof x === 'number' && typeof y === 'number') {

return x + y

}

return `${x}${y}`

}

instanceof 关键字

只能用于 类 的类型。

问:typeof 和 instanceof 的区别?

function getDate(date: Date | string) {

if(date instanceof Date) {

// 如果date是Date的实例

return date.getDate()

}

return date

}

四、class

类在其他强类型的语言很常见、对部分前端的小伙伴来说获取有些陌生。类是一个思想的转变。

1、成员变量/方法的权限

public、private、protected、readyonly

public默认值,表示公开的,外部和子类都能访问到。

private私有的,表示只能本类中访问,外部和子类都不能访问。

protected外部不能访问,子类可以访问到。

readyonly表示只读,不能被修改。

2、static 修饰符

普通的变量/方法,在类被实例化的时候创建。static 修饰的变量/属性是挂载在类本身,实例化之后不能通过this访问到static修饰的成员。

创建一个单例

// 静态属性 static

// ****** 创建 class 单例的方法 ******

// static 可以创建在类本身,而不是new出来的实例上

class Demo {

private static instance: Demo

public name: string

// 构造函数私有化,就相当于不能实例化

private constructor(name: string) {

this.name = name

}

public static getInstance() {

// 第一次创建的时候就会把instance挂在到Demo本身上去,第二次就不会再创建实例

if(!this.instance) {

this.instance = new Demo("hello")

}

return this.instance

}

}

const demo1 = Demo.getInstance()

const demo2 = Demo.getInstance()

// 单例

console.log(demo1.name) // hello

console.log(demo2.name) // hello

3、抽象类

抽象类用 abstract修饰,类似于interface的功能。

// 抽象类 用 abstract 修饰

// 类中用 abstract 修饰之后变成抽象方法

// 抽象类只能被继承

abstract class Gemo {

constructor(public width: number, height: number ) {

}

abstract getArea(): number

public setWidth(width:number) {

this.width = width

}

}

class Circle extends Gemo {

public getArea(): number {

return this.width

}

}

// interface 可以继承 类似于抽象类相同的功能 将相同的提取出来

五、泛型

1️⃣ 泛型的作用

泛型是用来解决传入类型不固定的问题,比如说传入的类型可能是string、number…或者方法返回的类型有可能是number、string等等,那么就可以约定一个泛型来表示该类型,一般用T表示。除了T之外,常见泛型变量代表的意思:

K(Key):表示对象中的键类型;V(Value):表示对象中的值类型;E(Element):表示元素类型。

2️⃣ 基本使用

在类中使用泛型

/*

类泛型: 类型不确定的时候,指定类型

*/

interface Item {

name: string

}

class DataManager {

// 传入的是 包含 T 的数组

constructor(private data: T[]) {}

public getData(index: number) {

// 根据下包获取数组对应的值

return this.data[index]

}

}

const dataArr = new DataManager([{name: "lisi"}])

console.log(dataArr.getData(0))

在接口中使用泛型

interface KeyValue {

key: K;

value: V;

}

3️⃣ 泛型工具类型

泛型表示任意类型,当在某处调用了该值下的一个属性,但是该类型是不一定有该属性,所以会发生不可预期的错误,如:

function fn(param: T): void {

// param 不知道是什么具体的解构

// console.log(param.length)

// 所以需要增加一些工具来判断参数是什么结构

}

为了更好的约束泛型,使用一些工具来增强泛型的可读性。

1、typeof

可以获取变量或者属性的类型,也可以获取函数的类型。

interface Person {

name: string

age: number

getName: () => string

}

const lisi: Person = {name: '李四', age: 18, getName: () => '李四'}

type Teacher = typeof lisi // 类型 Teacher = 类型 Person

type Fn = typeof lisi.getName // 此处 fn是函数类型:() => string

const getNickname: Fn = () => {

return '常山赵子龙'

}

2、keyof

获取类型的所有键,返回的是键的联合类型。

interface Person {

name: string

age: number

getName: () => string

}

type key1 = keyof Person // 'name' | 'age' | 'getName'

type key2 = keyof Person[] // 数组的键 如:'length' | 'toString' | 'pop' | 'push' | 'concat' | 'join'

type key3 = keyof string // string类型的键 如:number | 'toString' | 'charAt' | 'charCodeAt' | 'concat'...

当键是索引类型的时候

键是string类型keyof返回的类型是 string | number

键是number类型keyof返回的类型是 number

// 当是索引类型的时候

type key4 = keyof { [k: string]: boolean } // string | number

type key5 = keyof { [k: number]: boolean } // number

如何使用keyof?

假设我们有以下函数:

function prop(obj, key) {

return obj[key];

}

函数中obj和key是不确定的,所以是不能确定是否可以执行 obj[key] 这个操作。 进一步优化:

function prop(obj: object, key: string) {

return (obj as any)[key];

}

很显然加上 any 虽然可以解决燃眉之急,但这任然不是一个好的方案,还是不能确定是否可以执行 obj[key] 这个操作。用keyof可以解决这个问题:

function prop(obj: T, key: K) {

return obj[key];

}

不管obj中有什么属性,key总能找到对应键值。

interface Person {

name: string

age: number

}

const lisi: Person = {

name: '李四',

age: 18

}

function prop(obj: T, key: K) {

return obj[key];

}

const myName = prop(lisi, 'name') // myName: string

// const myName = prop(lisi, 'nickname') // 传入不存在的键值,则报错

3、in

用来遍历枚举类型。

enum Words {

A,

B,

C

}

type AliasWords = {

[w in Words]: string

}

// AliasWords 的类型等价为

// type AliasWords = {

// 0: string;

// 1: string;

// 2: string;

// }

4、infer

在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用。

type ReturnType = T extends (

...args: any[]

) => infer R ? R : any;

infer R 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。

5、extends

extends 就是类型继承,和值的继承了类似,用在类型中增加类型的约束。

interface Todo {

id: number

}

function add(todo: T): T {

return todo

}

const task = {

id: 1,

add: () => {

console.log('增加一项任务')

}

}

// 传入的 task 必须包含 id 这个字段

const myTask = add(task)

myTask.add()

4️⃣ 内置工具类型

TS 内置了一些类型的操作符,方便我们快速的操作类型。首字母都是大写。

1、Partial

Partial 可以把类型的所有属性变成可选。

interface User {

id: number

name?: string

status: number

super: boolean

}

type Coder = Partial

// Coder 的类型变成

// type Coder = {

// id?: number | undefined;

// name?: string | undefined;

// status?: number | undefined;

// super?: boolean | undefined;

// }

const lisi: Coder = {}

注意的是,Partial 只能处理一层的数据,当有嵌套的二层数据不会进行处理。

2、Required

和 Partial 相反,Required是将所有属性变成必选。

3、Readonly

Readonly是将所有属性变成只读。

4、Pick

定义

type Pick = { [P in K]: T[P]; }

可见是需要传入两个参数。如以下例子:

interface User {

id: number

readonly name?: string

status: number

super?: boolean

}

type Coder = Pick

// Coder 的类型变成

// type Coder = {

// id: number;

// }

// 必传 id 属性

const lisi: Coder = {id: 1}

5、Record

用来批量赋予某个类型。

interface User {

id: number

readonly name?: string

status: number

super?: boolean

}

type Leader = "zhangsan" | "lisi" | "wangwu"

type Coder = Record

// Coder 的类型变成

// type Coder = {

// zhangsan: User;

// lisi: User;

// wangwu: User;

// }

6、ReturnType

获取函数返回值的类型。

type getMoney = () => number

// 上面的函数返回值是 number

const money: ReturnType = 1000000

7、Exclude

将 某个类型 从类型集中剔除。

type ManyTypes = boolean | string | number

type newTypes = Exclude

// 剩下类型

// type newTypes = string | number

8、Extract

和 Exclude 相反,Extract从类型集中提取某个类型

type ManyTypes = boolean | string | number

type newTypes = Extract

// 剩下类型

// type newTypes = boolean

9、Omit

Omit 剔除某个属性,剩余的组建新的类型。

interface User {

id: number

readonly name?: string

status: number

super?: boolean

}

type Coder = Omit

// Omit 剔除 status 属性 剩余以下属性

// type Coder = {

// id: number;

// readonly name?: string | undefined;

// super?: boolean | undefined;

// }

10、NonNullable

NonNullable 的作用是用来过滤类型中的 null 及 undefined 类型。

type Name = string | undefined | null

type UserName = NonNullable // UserName 的类型为 string

11、Parameters

Parameters 的作用是用于获得函数的参数类型组成的元组类型。

type Param = Parameters<(name: string, age: number) => void>

// 类型 Param 为: type Param = [name: string, age: number]

六、tsconfig.json介绍

tsconfig.json 是 TypeScript 项目的配置文件。使用 tsc xxx.ts 命令可以通过配置文件编译出相应的js代码。

1、tsconfig.json 重要字段

files - 设置要编译的文件的名称;include - 设置需要进行编译的文件,支持路径模式匹配;exclude - 设置无需进行编译的文件,支持路径模式匹配;compilerOptions - 设置与编译流程相关的选项。

2、compilerOptions 选项

{

"compilerOptions": {

/* 基本选项 */

"target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'

"module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'

"lib": [], // 指定要包含在编译中的库文件

"allowJs": true, // 允许编译 javascript 文件

"checkJs": true, // 报告 javascript 文件中的错误

"jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'

"declaration": true, // 生成相应的 '.d.ts' 文件

"sourceMap": true, // 生成相应的 '.map' 文件

"outFile": "./", // 将输出文件合并为一个文件

"outDir": "./", // 指定输出目录

"rootDir": "./", // 用来控制输出目录结构 --outDir.

"removeComments": true, // 删除编译后的所有的注释

"noEmit": true, // 不生成输出文件

"importHelpers": true, // 从 tslib 导入辅助工具函数

"isolatedModules": true, // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).

/* 严格的类型检查选项 */

"strict": true, // 启用所有严格类型检查选项

"noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错

"strictNullChecks": true, // 启用严格的 null 检查

"noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误

"alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'

/* 额外的检查 */

"noUnusedLocals": true, // 有未使用的变量时,抛出错误

"noUnusedParameters": true, // 有未使用的参数时,抛出错误

"noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误

"noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

/* 模块解析选项 */

"moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)

"baseUrl": "./", // 用于解析非相对模块名称的基目录

"paths": {}, // 模块名到基于 baseUrl 的路径映射的列表

"rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容

"typeRoots": [], // 包含类型声明的文件列表

"types": [], // 需要包含的类型声明文件名列表

"allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。

/* Source Map Options */

"sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置

"mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置

"inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件

"inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

/* 其他选项 */

"experimentalDecorators": true, // 启用装饰器

"emitDecoratorMetadata": true // 为装饰器提供元数据的支持

}

}

七、最后

我是菜鸡,一步一步往上爬。

相关阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: