2021系统全面学习ts笔记,日常开发够用了!

325 阅读9分钟

一、基本类型

// 1、布尔值
let isFlagboolean;

// 2、数字
let sum: number;

// 3、字符串
let name: string;

// 4、symbol
let symbol1: symbol;

// 5、Null 和 Undefined
let n``null``;

// 6、数组
let colorArr: string[];

/* 基本类型有初始值的话,无需声明类型,ts在分配属性时会推断基础类型 */

二、特殊类型

  1. any

在不确定类型的时候使用any,但是any会绕过ts的类型检查,如果有问题,编译时不会报错,运行时才会报错

let notSure: any = 4 // ts类型检查会建议你替换为unknown

2.unknown

被称为顶端类型,是一些超类的集合,任何类型都可以赋值为unknown

// 使用any
let unSure: any = 22;
unSure.push(33); // 编译不会报错,运行报错
console.log(unSure);


// 使用unknown
let unSure: unknown = 22;
unSure.push(33); // 编译器认为这个类型没有push方法,就会报错
console.log(unSure);


// 使用unknown加类型判断
let unSure: unknown = 22;
if (unSure instanceof Array) {
  unSure.push(33);
}
console.log(unSure);     

3.never

表示永不存在的值,任何值都不能冠以类型never,被称为底端类型

// 函数永远不会有返回值时
function fn(): never {
  throw new Error("error");
}
fn();

// 可以用作收窄类型
type Foo = string | number;

// type Foo = string | number | boolean; // 如果添加boolean类型

function checkType(foo: Foo): void {
  if (typeof foo === "string") {
  
  } else if (typeof foo === "number") { 
  
  } else {
    // foo 在这里是 never
    const check: never = foo; // 修改Foo类型会报错: Type 'boolean' is not assignable to type 'never'.
    console.log(check);
  }
}

/* 使用 never 可以在新增了类型之后,编译时检查出没有对应的类型判断,其目的就是写出类型绝对安全的代码。*/

如何在 never、unknown、any 之间作出选择:

  • 永远不能取得任何值的时候使用 never
  • 定义或者获取到得的任意值,但不确定类型的时候,使用unknown
  • 除非有意忽略类型检查,否则一般不要使用any,不安全 4.void

表示没有任何类型,一般表示函数没有返回值

function setTitle(title: string): void {
    console.log(title)
}

void和never的区别:

  • 没有设置返回值的函数,是会隐式返回 undefined。尽管我们通常说这样的函数 “什么也不返回”,但实际上它是会返回的。在这些情况下,我们通常忽略返回值。在 TypeScript 中这些函数的返回类型被推断为 void

  • 具有 never 返回类型的函数永不返回。它也不返回 undefined。而且函数没有不会正常完成,也就是说它可能会抛出异常或根本无法退出执行。

5.enum

// 数字类型枚举
enum VipCard {
  year,
  month,
  week,
}

const year = VipCard.year;
console.log(year, "year"); // 0


// 修改数字关联枚举值
enum VipCard {
  year=4,
  month,
  week,
}

const year = VipCard.year;
console.log(year, "year"); // 4

console.log(VipCard.month, "month"); // 5
console.log(VipCard.week, "week"); // 6


// 字符串类型枚举
enum VipCard {
  year,
  month,
  week,
}

const year = VipCard[0];
console.log(year, "year"); // 'year'


// 字符串枚举关联值定义
enum CONSTANT {
  PEOPLETYPE = 'part',
}
const year = CONSTANT.PEOPLETYPE;
console.log(year, "year"); // 'part'

6.tuple

表示一个已知元素数量和类型的数组

const peple: [number, string] = [18, "xiaoming"];
const age = peple[0];
const useName = peple[1];
peple[2] = "nicName"; // 报错 Type '"nicName"' is not assignable to type 'undefined'.

console.log(age, useName);

7.type

自定义类型,自己组合定义类型

// 自定义类型
type Product = string | undefined;
const math: Product = "math";

type People = {
  readonly name: string; // 只读属性
  age: number;
  nickname?: string; // 可选属性
  [propName: string]: unknown; // 额外属性
};

const xiaoMing: People = {
  name: "xiaoMing",
  age: 12,
  uid: "98989e3e3",
};

// type也可以定义像接口一样的对象,也可以定义基础类型,interface不可以,在声明合并方面,interface可以,type不行

三、接口

// 接口基础使用方式
interface People {
  readonly name: string; // 只读属性
  age: number;
  nickname?: string; // 可选属性
  [propName: string]: unknown; // 额外属性
}

const xiaoMing: People = {
  name: "xiaoMing",
  age: 12,
  uid: "98989e3e3",
};
xiaoMing.name = "Lili"; // 报错 Cannot assign to 'name' because it is a read-only property.


// 函数接口
interface SearchFunc {
  (source: string, subString: string): boolean;
}
const mySearch: SearchFunc = function (source, subString) {
  const result = source.search(subString);
  return result > -1;
};

// 类接口
interface Alarm {
    alert(): void;
}

interface Light {
    lightOn(): void;
    lightOff(): void;
}

class Car implements Alarm, Light { // 实现多个接口
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

// 接口继承接口
interface Alarm {
  alert(): void;
}

interface LightableAlarm extends Alarm {
  lightOn(): void;
  lightOff(): void;
}
class Car implements LightableAlarm {
  lightOn(): void {
    throw new Error("Method not implemented.");
  }
  lightOff(): void {
    throw new Error("Method not implemented.");
  }
  alert(): void {
    throw new Error("Method not implemented.");
  }
}

四、函数

// 函数类型定义  可选参数放在最后
function buildName(lastName = "Smith", firstName?: string): string {
  return lastName + " " + firstName;
}
console.log(buildName("a", "b")); // a b


// 剩余参数 个数不限的参数
function buildName2(
  lastName = "Smith",
  firstName?: string,
  ...restOfName: string[]
): string {
  return firstName + " " + lastName + restOfName.join(" ");
}
console.log(buildName2("a", "b")); // a bc


// 重载 函数名称相同 但输入输出类型或个数不同的子程序,它可以简单地称为一个单独功能可以执行多项任务的能力
// Ts为同一个函数提供多个函数类型定义来进行函数重载,目的是重载的函数在调用的时候会进行正确的类型检查,让你清晰的知道传入不同的参数得到不同的结果
function add(x: string, y: string): string;
function add(x: number, y: number): number;

function add(x: string | number, y: number | string): number | string {
  if (typeof x === "string" && typeof y === "string") {
    return x + "," + y;
  } else if (typeof x === "number" && typeof y === "number") {
    return x + y;
  } else {
    return "";
  }
}
const x = add("1", "2"); // string
const y = add(2, 3); // number
console.log(x, y);

/* 需要遵循的原则是:越具体的类型,应该定义在越前面。这样有利于 TypeScript 编译器推断出更准确的类型 */

五、类

// 继承
class Animal { // 基类
  name: string;
  constructor(theName: string) {
    this.name = theName;
  }
  move(distanceInMeters = 0) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

class Snake extends Animal { // extends关键字实现派生类
  constructor(name: string) {
    super(name);
  }
  move(distanceInMeters = 5) { // 方法重写
    console.log("Slithering...");
    super.move(distanceInMeters);
  }
}

class Horse extends Animal { // 派生类
  constructor(name: string) {
    super(name);
  }
  move(distanceInMeters = 45) { // 方法重写
    console.log("Galloping...");
    super.move(distanceInMeters);
  }
}

const sam = new Snake("Sammy the Python");
const tom = new Horse("Tommy the Palomino");

sam.move(); // Slithering...  Sammy the Python moved 5m.
tom.move(34); // Galloping... Tommy the Palomino moved 34m.

/*派生类包含了一个构造函数,它 必须调用 super(),它会执行基类的构造函数。 而且,在构造函数里访问this的属性之前,一定要调用 super()。 这是TypeScript强制执行的一条重要规则。*/


// 修饰符
class Animal {
  private name: string; // 私有属性,不能直接访问
  protected nickName: string; // 保护变量,不能直接访问, 在派生类中可以访问
  readonly numberOfLegs: number = 8; // 只读属性不能更改
  age: number | undefined; // 没有声明修饰符,默认都是public
  public constructor(theName: string, nickName: string) {
    this.name = theName;
    this.nickName = nickName;
  }
  public getName(): string {
    return this.name;
  }
}
class Horse extends Animal {
  private subAge: string;
  constructor(theName: string, nickName: string) {
    super(theName, nickName);
    this.subAge = nickName;
  }
  public getNickName(): string {
    // return this.theName; // Error Property 'name' is private and only accessible within class 'Animal'.
    return this.nickName;
  }
}


new Animal("Cat", "1").numberOfLegs = 10; // 错误: Cannot assign to 'numberOfLegs' because it is a read-only property.
new Animal("Cat", "2").nickName; // 错误: Property 'nickName' is protected and only accessible within class 'Animal' and its subclasses
new Animal("Cat", "3").name; // 错误: Property 'name' is private and only accessible within class 'Animal'.

const howard = new Horse("Cat", "4");
console.log(howard.getNickName(), "nickName");

// 抽象类 指不具体的类,通常作为基类使用,不能直接实例化,关键字abstract声明 规定所有继承的子类必须实现他规定的功能和相关的操作
abstract class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  abstract eat(): void;
}


class Dog extends Animal {
  constructor(name: string) {
    super(name);
  }
  eat() {
    console.log(this.name + "吃肉");
  }
}

const dog = new Dog("狗");
dog.eat();

六、联合类型和交叉类型

// 联合类型表示一个值可以是几种类型之一, 用竖线|分隔每个类型
interface Bird {
  fly(): void;
  layEggs(): void;
}


interface Fish {
  swim(): void;
  layEggs(): void;
}

function getSmallPet(): Fish | Bird {
  return {
    layEggs: () => {
      console.log("ss");
    },
    swim: () => {
      console.log("ss");
    },
    fly: () => {
      console.log("dee");
    },
  };
}

const pet = getSmallPet();
pet.layEggs(); // okay
pet.swim(); // Property 'swim' does not exist on type 'Bird | Fish'.
pet.fly(); // Property 'fly' does not exist on type 'Bird | Fish'


// 交叉类型 使用符号&将多个类型合并为一个类型
interface IPerson {
  id: string;
  age: number;
}

interface IWorker {
  companyId: string;
}

type IStaff = IPerson & IWorker; // 可以用type去自定义交叉类型

const staff: IStaff = {
  id: "E1006",
  age: 33,
  companyId: "EXE",
};

console.dir(staff); // {age: 33companyId: "EXE"id: "E1006"}

七、类型断言和类型保护

// 类型断言  保证和检测数据是否符合我们的要求
interface Foo {
  bar: number;
  bas: string;
}

/* 需要给对象初始化一个值是空对象,得给每一个属性设置初始值,但有的属性初始值不确定 */
const foo: Foo = {
  bar: 0,
  bas: "",
};
const foo = {} as Foo; // 用 as 告诉编译器你知道你在做什么


// 类型保护 
/* 在js中,有时候我们去访问对象的某个属性,但是可能这个属性不存在,那么就会为undefind,有时候会不符合我们的预期,
因此为了保证类型的安全,判断未知数据是不是所需类型,ts添加了类型保护,指定数据类型,通过 typeof、instanceof、in以及自定义类型保护的类型谓词 */
function isNumber(x: any): x is number {
  return typeof x === "number";
}

八、模块

// 定义外部模块类型,创建文件.d.ts
declare module "path" {
    export function normalize(p: string): string;
    export function join(...paths: any[]): string;
    export let sep: string;
}

declare module 'clipboard';

九、命名空间

// 区分不同的模块,比如说同样的音频类,含有同名方法、变量,可以做命名空间上的区分
/* eslint-disable @typescript-eslint/no-namespace */

export namespace Audio {
  export class Audio1 {
    /* ... */
  }
  export class Audio2 {
    /* ... */
  }
}

十、泛型

// 举例,约束的函数的参数和返回值
function identity (value: Number) : Number {
  return value;
}

console.log(identity(1)) // 1

// 扩展成为通用类型,就可以使用泛型,设计泛型的关键目的是在成员之间提供有意义的约束
function identity<T>(value: T): T {
  return value;
}

console.log(identity<string>('name')); // 'name'

任何有效的字母有可以做泛型符号,常见的泛型通用符号

  • T (Type),在定义泛型时通常用作第一个类型变量名称
  • K(Key):表示对象中的键类型;
  • V(Value):表示对象中的值类型;
  • E(Element):表示元素类型。
// 泛型可定义任何数量的类型变量
function test<T, U>(value: T, message: U): T {
  console.log(message);
  return value;
}

console.log(test<number, string>(68, "测试")); //测试 68

/* 可以不用指定number和string的类型,编译器自己会识别 */
console.log(test(68, "测试")); //测试 68

十一、声明合并

// 无函数成员合并接口
interface Box {
  height: number;
  width: number;
}

interface Box {
  scale: number;
  width: string; // 声明同一个属性的不同类型会报错
}

const box: Box = { height: 5, width: 6, scale: 10 };


// 有函数成员合并接口 同名函数会被视为重载
interface Cloner {
    clone(animal: Animal): Animal;
}

interface Cloner {
    clone(animal: Sheep): Sheep;
}

interface Cloner {
    clone(animal: Dog): Dog;
    clone(animal: Cat): Cat;
}

// 合并命名空间
namespace Animals {
    export class Zebra { }
}

namespace Animals {
    export interface Legged { numberOfLegs: number; }
    export class Dog { }
}

十二、Ts的优缺点

  • 优点

    1、代码的可读性和可维护性:定义后端接口返回值,编码时,编辑器会提醒接口返回值的类型

    2、在编译阶段就发现大部分错误,避免了很多线上bug

  • 缺点

    1、有一定的学习成本,需要理解很多强类型语言有的概念

    2、增加开发成本,项目工期紧张,定义类型会带来很多代码开发量

    3、兼容没有类型的js库,需要增加很多声明文件

图片来源于网络,文章内容参考网上资料,也有自己的测试案例,如有任何问题,请联系更正或者删除