TypeScript详细总结归纳!!!

一、前言

学完一个东西,总要总结一下才能巩固,好记性不如烂笔头。本文想对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

  • 默认情况下

    可以把 undefinednull 赋值给其他类型

// 未开启 "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<string> = ['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只能赋值给unknownany

// 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<string> = ['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> {
    // 传入的是 包含 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<K, V> {
  key: K;
  value: V;
}

3️⃣ 泛型工具类型

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

function fn<T>(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<T extends object, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

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

interface Person {
    name: string
    age: number
}

const lisi: Person = {
    name: '李四',
    age: 18
}

function prop<T extends object, K extends keyof T>(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> = T extends (
  ...args: any[]
) => infer R ? R : any;

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

5、extends

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

interface Todo {
    id: number
}
function add<T extends Todo>(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<User>
// 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<T, K extends keyof T> = { [P in K]: T[P]; }

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

interface User {
    id: number
    readonly name?: string
    status: number
    super?: boolean
}

type Coder = Pick<User, "id">
// 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<Leader, User>
// Coder 的类型变成
// type Coder = {
//     zhangsan: User;
//     lisi: User;
//     wangwu: User;
// }

6、ReturnType

获取函数返回值的类型。

type getMoney = () => number
// 上面的函数返回值是 number
const money: ReturnType<getMoney> = 1000000 

7、Exclude

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

type ManyTypes = boolean | string | number

type newTypes = Exclude<ManyTypes, boolean>
// 剩下类型
// type newTypes = string | number

8、Extract

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

type ManyTypes = boolean | string | number

type newTypes = Extract<ManyTypes, boolean>
// 剩下类型
// type newTypes = boolean

9、Omit

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

interface User {
    id: number
    readonly name?: string
    status: number
    super?: boolean
}

type Coder = Omit<User, "status">
// Omit 剔除 status 属性 剩余以下属性
// type Coder = {
//     id: number;
//     readonly name?: string | undefined;
//     super?: boolean | undefined;
// }

10、NonNullable

NonNullable 的作用是用来过滤类型中的 nullundefined 类型。

type Name = string | undefined | null
type UserName = NonNullable<Name> // 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          // 为装饰器提供元数据的支持
  }
}

七、最后

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值