⚡TS手册,没事可以翻翻

1,180 阅读10分钟

前言

我JS用的很顺手,为什么要用TS呢? 这不是会白白多了很多工作量吗???

相信很多小伙伴都有这个疑问,翻了网上大神的回答再结合工作中的经验,主要有以下三点

  • 面向项目:

    • TS - 面向解决大型项目,架构设计以及代码维护有一定的优势

      • 比如某个函数的传参定义为number, 函数内部的逻辑也是基于number类型开发的,这时候其他人传个string,函数内部逻辑就会出错,TS可以从起点就规避这种场景
    • JS - 脚本化语言,简单场景且轻量化

  • 自主检测

    • TS - 编译时检测,主动发现并纠正错误(静态类型弱类型)
    • JS - 运行时报错(动态类型弱类型)
  • 运行流程

    • TS - 依赖编译,依靠编译打包实时生成浏览器可以运行的js
    • JS - 可直接在浏览器中运行

其实,以上都是次要的,主要现在很多公司的岗位都要求会TS,哈哈~~~


本篇参考了一些大佬的真传干货,汇总提炼出的TS方面的知识点,看完可以上手常规的TS项目啦~~~

image.png

正文开始

武器库

在线直接写ts: 传送门

ts文档: 传送门

1,变量声明

五种基本类型都在下面啦~~~

给变量定义了什么类型,值就要是什么类型,要进行统一的

let a: number = 1; //number小写
let b: string = "1";
let c: undefined = undefined;
let d: null = null;
let e: boolean = true;

如果不按照规范写的话,会报如下的错误:

image.png

2,枚举声明 - enum

2.1,数字类枚举

不赋值就是数字类枚举,默认从零开始,依次递增

 enum Score {
        BAD,  // 0
        NG,   // 1
        GOOD, // 2
        PERFECT, // 3
    }
    let score:Score = Score.BAD;

BAD的值为 1,NG值为2

 enum Score {
        BAD = 1,  // 1
        NG,   // 2
        GOOD, // 3
        PERFECT, // 4
    }

2.2,字符串枚举

enum Score {
        BAD = 'BAD',
        NG = 'NG',
        GOOD = 'GOOD',
        PERFECT = 'PERFECT',
    }

使用场景

enum Role {
  Admin = "Admin",
  User = "User",
  Guest = "Guest",
}
type User = {
  name: string;
  role: Role;
};
function getUserRole(data: User): string {
  switch (data.role) {
    case Role.Admin:
      return `Welcome Admin, ${data.name}! You have full access.`;
    case Role.User:
      return `Hello User, ${data.name}.`;
    case Role.Guest:
      return `Hello Guest, ${data.name}.`;
  }
}
getUserRole({ name: "123", role: Role.Admin });

2.3,常量枚举(不常用)

在编译阶段会被删除

const enum Directions {
  Up,
  Down,
  Left,
  Right,
}
let directions1 = [
  Directions.Up,
  Directions.Down,
  Directions.Left,
  Directions.Right,
];

生成的代码 var directions = [0 /* Up /, 1 / Down /, 2 / Left /, 3 / Right */];

普通枚举

enum Directions {
     Up,
     Down,
     Left,
     Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]

生成的代码 var directions = { Up: 0, Down: 1, Left: 2, Right: 3 },

2.4,反向映射(不常用)

指的是取值的时候,因为做了双向映射,所以 Score[0] 的值是 BAD

 enum Score {
        BAD,
        NG,
        GOOD,
        PERFECT,
    }

    let scoName = Score[0]; // BAD
    let scoVal = Score['BAD'];  // 0

2.5,异构(不常用)

enmu Enum {
        A,  // 0
        B,  // 1
        C = 'C',
        D = 'D',
        E = 6,
        F,  // 7 逐个递增,上面一个是数字类型6,所以它是7
    }

3,Symbols声明

不可改变且唯一,一身傲骨宁折不弯就是它

let sym2 = Symbol("key");
let sym3 = Symbol("key");
sym2 === sym3; // false, symbols是唯一的

使用场景:用作对象的key

let sym = Symbol();
let symbolObj = {
  [sym]: "value",
};
console.log(symbolObj[sym]); // "value"

有一些自己的功能函数

  • Symbol.hasInstance
  • Symbol.iterator
  • Symbol.replace
  • Symbol.match等

4,联合类型和交叉类型

他两兄弟可以一起记,差不多是个反义词

  • 联合类型:或,满足多个类型中的一个
  • 交叉类型:并且,满足多个类型

联合类型

let arr: Array<number | string> = [1, "1"];

如下图,arr可以是数值,可以是字符串,也可以同时是数值和字符串,就是不能是其他的

image.png

image.png

交叉类型

let arr: Array<number & string> = [1, "1"];

如下图, arr必须同时是数值和字符串 两种类型才可

image.png

5,定义数组

定义数组类型的两种方式

//普通数组
let arr1: number[] = [1, 1];
let arr2: Array<number> = [1, 1];

定义联合类型,让数组的成员可以存在多种类型

// 联合类型(和元组的达成结果一样)
let arr3: Array<number | string> = [1, "1"];

6,定义元祖

数组合并了相同类型的对象,元祖合并了不同类型的对象

let x: [number, string];
x = [5, "abc"];
let y: [number, string] = [5, "abc"];

7,定义对象

interface Phone {
  name: string; //必填
  size: number; //必填
  color?: string; //选填
  action(): void; //必填
  [propName: string]: any; //任意属性
}
let obj: Phone = {
  name: "诺基亚",
  size: 100,
  action: function () {},
};
let obj1: Phone = {
  name: "诺基亚",
  size: 100,
  action: function () {},
  color: "blue",
  other: "测试",
};

7.1, [propName: string]: any;的使用场景

如果有很多参数定义,但是你不想全部都定义,那你就是可以定义几个主要的,其他用这个表示就行了。

还在疑惑为啥这里不解释下interface的使用场景吗,下文有专门的区域讲解的~~~

8,定义函数

8.1,函数入参的声明

// 第一种
function fuc1(a: string, b: string) {
  return ["1", "2"];
}
fuc1('a', 'b')
// 第二种
interface MyParams {
  x: number;
  y: number;
}
function fuc2(params: MyParams) {
  return ["1", "2"];
}
fuc2({x: 1, y: 2})

8.2,函数检查类型

场景:项目有一个公共函数传参类型(SearchFun),后续的函数继承这个接口后,都得按照这个规范来

interface SearchFun {
  (a: number, b: number): boolean;
}
let fuc6: SearchFun = function (c: number, d: number): boolean {
  return c > d;
};

fuc6(1, 2);

如果没按规则来的场景如下:

image.png

8.3,函数出参的声明

声明出参为字符串(): string

function fuc2(a: number | string, b: number): string {
  return a + "" + b;
}
fuc2('1', 2)

9,定义类

  • public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的;
  • private 修饰的属性或方法是私有的,不能在声明它的类的外部访问;
  • protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的;
  • static 不需要实例化 调用类,直接调用
class Cat2 {
  name: string;
  color: string;
  constructor(name, color) {
    this.name = name;
    this.color = color;
  }
  static eat() {
    return "吃";
  }
}
var c2 = new Cat2("黑猫", "黑");
c2.name;
Cat2.eat();

10,interface和type的异同

10.1,先来两句实际工作中的使用诀窍

  • 写法类似,通常用 interface,其他无法满足需求的情况下用 type。

  • type不会声明合并,interface会声明合并。

10.2,异同之处:

type

  • 可以描述对象和函数

  • 可以被继承

  • 可以声明基本类型,联合类型,交叉类型,元组

  • 不会声明合并(重复声明会报错提示)

  • 不会有索引签名问题

interface

  • 可以描述对象和函数

  • 可以被继承

  • 不可以声明基本类型,联合类型,交叉类型,元组

  • 会声明合并(声明过的接口可以再次声明,进行合并)

  • 有索引签名问题

type声明基本类型联合类型交叉类型数组元组的案例也给小伙伴放下面了

type userName = string; // 基本类型 
type userId = string | number; // 联合类型
type arr = number[]; // 数组
// 元组
type tuple = [string, boolean];
let tupleType: tuple;
tupleType = ["cluo", true];

// 对象类型 
type Person = { 
  id: userId; // 可以使用定义类型 
  name: userName; 
}; 
const user: Person = { 
  id: "901", 
  name: "椿"
};
// 泛型 
type Tree<T> = { value: T };

10.3,声明合并

声明合并指的是可以声明多次,重名的会合并

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

let user: Person = { 
  name: "cluo", 
  age: 666, 
};

喝口水,休息一会吧~~~

image.png

11,泛型

定义的时候不指定类型,用的时候指定类型

function fuc3<T>(a: T, b: T): T[] {
  return [a, b];
}
fuc3<number>(1, 2);
// 或者
fuc3<string>('a', 'b');
// 或者 
fuc3("a", "b");

上面的代码片段中,fuc3<T>中的T表示任意类型,供入参和出参使用。

用的时候可指定number 或者 string,函数的入参和出参类型也会相应改变。

因为ts里有类型推论,自动推测传入的'a'是字符串,把T设为了string

11.1,不同类型的函数


function fuc4<T>(a: T, b: T): string {
  return a + "" + b + c;
}
fuc4<number | string>(1, "2");

11.2,泛型约束

泛型不支持number,因为number没有length

错误的

function f4<T>(arg: T): T {
 console.log(arg.length); // 错误: T不存在length属性 比如数值等
}
f4<number>(123)

正确的

interface LengthN {
  length: number;
}
function myHello<T extends LengthN>(arg: T): T {
  //约束了传参必须包含length属性的类型
  // console.log(arg.length);
  return arg;
}
myHello<string>("123");

12,类型断言

使用场景:通常发生在你比TS更知道某个值的类型

这时,就可以使用类型断言解决TS报错。

  • 尖括号写法
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

下面的padding根据不同场景可以为number 可以为string,但是在下面的场景,我们自己知道肯定为number

function padLeft(value: string, padding: string | number) {
  // 报错: Operator '+' cannot be applied to 
  // types 'string | number' and 'number'
  return Array(padding + 1).join(" ") + value;
}

改为

function padLeft(value: string, padding: string | number) {
  return Array(padding as number + 1).join(" ") + value;
}

13,类型守卫

书接上文,有些场景as写的过多未免有些劳累。于是,有了类型守卫

主要有三种

  • typeof: 用于判断 "number","string","boolean"或 "symbol" 四种类型.
  • instanceof : 用于判断一个实例是否属于某个类
  • in: 用于判断一个属性/方法是否属于某个对象
function padLeft(value: string, padding: string | number) {
  console.log((padding as number) + 3);
  console.log((padding as number) + 2);
  console.log((padding as number) + 5);
  return Array((padding as number) + 1).join(' ') + value;
}

13.1,用typeof可以改为如下:

function padLeft(value: string, padding: string | number) {
  if (typeof padding === 'number') {
      console.log(padding + 3); //正常
      console.log(padding + 2); //正常
      console.log(padding + 5); //正常 
      return Array(padding + 1).join(' ') + value; //正常
  }
  if (typeof padding === 'string') {
      return padding + value;
  }
}

13.2,instanceof的使用场景

class Man {
    handsome = 'handsome';
}
class Woman {
    beautiful = 'beautiful';
}

function Human(arg: Man | Woman) {
    if (arg instanceof Man) {
        console.log(arg.handsome);
        console.log(arg.beautiful); // 会报错,因为Man这个类里没有beautiful
    } else {
        // 这一块中一定是 Woman
        console.log(arg.beautiful);
    }
}

13.3,in的使用场景

interface Teacher {
        name: string,
        courses: string[];
    }

    interface Student {
        name: string,
        startTime: Date;
    }

    type Course = Teacher | Student;

    function startCourse(cls:Course) {
        if ('courses' in cls) {
            console.log('teacher', cls.courses);
        }
        if ('startTime' in cls) {
            console.log('student', cls.startTime);
        }
    }

14,any, unknown, void, never

14.1,any

绕过所有类型检查 => 类型检测和编译筛查全部失效

 let anyValue:any = 123;

    anyValue = 'anyValue';
    anyValue = false;

    let value1:boolean = anyValue;

14.2,unknown

绕过赋值检查 => 禁止更改传递

let unKnownValue:unknown;

    unKnownValue = true;
    unKnownValue = 123;
    unKnownValue = 'unKnownValue';

    let value1:unknown = unKnownValue; // OK
    let value2:any = unKnownValue;  // OK
    let value3:boolean = unKnownValue; // 报错

14.3,void

void(与any相反) => 声明函数返回值

function voidFunction():void {
        console.log('no return');
    }

14.4,never

永不返回任何东西 或者 永远抛出error

    function error(msg:string):never {
        throw new Error(msg);
    }
    function longlongloop():never {
        while(true) {
            // ……
        }
    }

15,常用内置工具类型

15.1,Record

定义一个对象的 key 和 value 类型


type AnimalType = 'cat' | 'dog' | 'frog';
type AnimalDescription { name: string, title: string }
const AnimalMap: Record<AnimalType, AnimalDescription> = {
    cat: { name: '猫', title: 'cat' },
    dog: { name: '狗', title: 'dog' },
    frog: { name: '蛙', title: 'wa' },
};

15.2,Partial

生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为可选项

interface Foo {
        name: string
        age: number
    }
    type Bar = Partial<Foo>
    // 相当于
    type Bar = {
        name?: string
        age?: number
    }

15.3,Required

生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为必选项

interface Foo {
        name: string
        age?: number
    }
    type Bar = Required<Foo>
    // 相当于
    type Bar = {
        name: string
        age: string
    }

15.4,Readonly

生成一个新类型,T 中的 K 属性是只读的,K 属性是不可修改的。

interface Foo {
        name: string
        age: number
    }
    type Bar = Readonly<Foo>
    // 相当于
    type Bar = {
        readonly name: string
        readonly age: string
    }

15.5,Pick

生成一个新类型,该类型拥有 T 中的 K 属性集 ; 新类型 相当于 T 与 K 的交集

白话:借别人家的粮食吃

interface Foo {
        name: string;
        age?: number;
        gender: string;
    }
    type Bar = Pick<Foo, 'age' | 'gender'>
    // 相当于
    type Bar = {
        age?: number
        gender: string
    }

15.6,Omit

Pick相反

生成一个新类型,该类型拥有 T 中除了 K 属性以外的所有属性

type Foo = {
        name: string
        age: number
    }

    type Bar = Omit<Foo, 'age'>
    // 相当于
    type Bar = {
        name: string
    }

15.7,Exclude

如果 T 是 U 的子类型则返回 never 不是则返回 T

取差集

type A = number | string | boolean
   type B = number | boolean

    type Foo = Exclude<A, B>
    // 相当于
    type Foo = string

15.8,Extract

和 Exclude 相反

取交集

type A = number | string | boolean
   type B = number | boolean

    type Foo = Extract<A, B>
    // 相当于
    type Foo = number | boolean

完结

这篇文章我尽力把我的笔记和想法放到这了,希望对小伙伴有帮助。

欢迎转载,但请注明来源。

最后,希望小伙伴们给我个免费的点赞,祝大家心想事成,平安喜乐。

image.png