TypeScripts 学习笔记

TypeScript 是拥有类型的JavaScript超集,它可以被编译成普通、干净、完整的JavaScript代码。

一、起步

1. TypeScripts的编译环境

1.1 安装

# 全局安装
npm install typescript -g

安装好后,

# 查看版本
tsc --version

# 编译代码,将ts文件编译成js文件
tsc xxx.ts

1.2 搭建环境

搭建本地的ts环境,学习所需

全局安装typescript后,在每次使用ts文件时,都必须将其手动转换成JS文件,才能正常运行。所以我们可以搭建环境,以便直接运行ts文件或自动编译ts文件。

Vue或React脚手架会自动创建好TS的环境。

1.2.1 ts-node

直接运行ts文件。

安装工具:

# 安装ts-node
npm i ts-node -g
# 安装ts-node所需依赖
npm i tslib @types/node -g

使用:

# 直接使用 ts-node,即可执行运行TS文件
ts-node xxxxxx.ts

1.2.2 搭建ts-webpack环境

自动编译和自动运行。

更多webpack知识可移步我的webpack文章博客

1.2.2.1 环境下载:
  1. 创建webpack环境

    npm i webpack webpack-cli -D
    
  2. 安装ts-loader

    npm i ts-loader typescript -D
    
  3. 安装插件

    # 通过html来引用ts编译后的文件,运行在浏览器上
    npm i html-webpack-plugin -D
    
  4. 安装自动编译更新服务

    npm i webpack-dev-server
    
    # package.json 中创建相应脚本
    # "build": "webpack",  // 打包
    # "serve": "webpack serve"	// devSever 运行
    
1.2.2.2 webpack配置

webpack.config.js

const path = require('path')
const htmlWebpackPlugin = require("html-webpack-plugin")

module.exports = {
  mode: "production",
  // mode: "development",
  entry: './src/main.ts',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'build.js'
  },
  resolve: {
    extensions: ['.ts', '...'],
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: "ts-loader"
      }
    ]
  },
  plugins: [
    new htmlWebpackPlugin({
      template: "./index.html"
    })
  ]
}
1.2.2.3 文件目录
├── dist (打包出口文件夹)
├── mode_modules
├── src
|   ├── math.ts
|   ├── main.ts   入口文件
├── index.html	  html模板
├── webpack.config.js

2. 变量的定义格式

格式参照:var/let/const 标识符: 数据类型 = 赋值;

const msg: string = "初使用typescript";
const age: number = 19;
const flag: boolean = true;
// ....

注意:

类型声明使用的是小写(例:string)而非大写(例:String)。string是TypeScript中定义的字符串类型,String是ECMAScript中定义的一个类。

给变量声明好类型后,再给变量赋值其他类型的值,那么就会报错。

声明变量的关键字,TypeScripts不推荐使用 var 来声明,因为var会导致各种作用域问题。

若出现无法重新声明块范围变量的错误,则在TS文件末尾添加:

export {};

将当前TS文件作为模块,不与其他TS文件共享作用域。

2.1 变量类型的推导

当在TS文件中,直接给变量赋值时:

let message = "消息";
// let message: string = "消息";

在一个变量第一次赋值时,TypeScripts会根据后面的赋值内容的类型,来推断出变量的类型。

3. 数据类型的使用

string、number、boolean类型的使用,较为简单,就不记录笔记了。就如变量定义格式一样基本使用就行。

3.1 array类型的使用

// 利用类型推导出的变量类型为 any[] 或 never[] (tsconfig中strict值有关)
// any[]即:可以存放任何类型的数据。
// never[]即:变量声明为空数组,不能添加任何值,
// 要想变为any[]则需const arr = [] as any[], 使用类型断言。
const arr = [];
arr.push('123');
arr.push(123);
console.log('arr', arr);

// 指定数组的类型
const arr1: number[] = [];
// 泛型定义
const arr2: Array<string> = [];
arr1.push(1);
arr1.push(2);
// arr1.push('3');  // 报错,是数字类型数组
arr2.push('1');
arr2.push('2');
// arr2.push(3); // 报错,是字符串类型数组

console.log('arr1', arr1);
console.log('arr2', arr2);

3.2 object类型的使用

可以使用接口interface定义。

const obj = {
  name: 'fct',
  age: 23
};
/*
// 自动推导类型
const obj: {
    name: string;
    age: number;
}
*/

console.log(obj.name);
console.log(obj.age);

3.3 null 和 undefined

null类型的变量只能接收null,而undefined也只能接收undefined。

let n1 = null; // n1自动推断为any(看tsconfig的配置)
const n2 = null; // n2 自动推导为null,且为const定义不能改变
n1 = 'abc';

const n3 = undefined; // 自动推导为null
let n4 = undefined; // 自动推导为any(看tsconfig的配置)

n4 = 123;

console.log(n1, n2, n3, n4);
const a1: null = null;
const a2: undefined = undefined;
export {};

3.4 any类型

在某些情况下,我们确实无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们可以使用any类型。

any类型过于开放..., 因此之后推出了unknown。

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay
// 可以赋值任何类型

3.5 unknown类型

unknownany 的主要区别是 unknown 类型会更加严格:在对 unknown 类型的值执行大多数操作之前,我们必须进行某种形式的检查。而在对 any 类型的值执行操作之前,我们不必进行任何检查。

unknown 类型只能被赋值给 any 类型和 unknown 类型本身。

// unknown类型可以被赋予任何值
let step: unknown;
let condition = false;
if (condition) {
  step = 'arr'
} else {
  step = 123
}

// const str: string = step; 
// const num: number = step; 
// 报错,不能将unknown类型赋值给其他类型(除any或unknown)

3.6 void类型

void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void

function warnUser() {
// 函数无返回值,会自动推断为void类型
// function warnUser(): void {
    console.log("This is my warning message");
}

3.7 never类型

never类型表示的是那些永不存在的值的类型。 例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never类型,当它们被永不为真的类型保护所约束时。

never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}

// 推断的返回值类型为never
function fail() {
    return error("Something failed");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {
    }
}

// let t: never = false; // no
let t1: string = (() => {
  throw new Error('TypeScript never');
})();

3.8 元组 Tuple

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为 stringnumber类型的元组。

let info: [string, number];
info = ['abc', 123];
console.log(info);
console.log(info[0].substring(0, 2));
// info[0] = 234;  // 不能将类型“number”分配给类型“string”
// 类型“number”上不存在属性“substring”。
// console.log(info[1].substring(0,1));

3.9 函数参数、返回值的数据类型

3.9.1 基本使用

// 函数参数定义类型,也可为返回值定义
function sum(num1: number, num2: number): number {
  return num1 + num2;
}
console.log(sum(1, 2));
// console.log(sum(1, '1')); // 报错

3.9.2 接收对象参数/可选类型

不写函数的返回类型,ts会自动推断。

函数的参数可设置为可选类型。相应的在对象里同样适用

// 函数接收对象类型
function printInfo(info: { name: string; age: number }) {
  console.log(info);
}
printInfo({ name: 'fct', age: 18 });
// printInfo({name: 'fct', age: '12'}) // age类型需为number
// printInfo({name: 'fct'}) // 缺少age属性

// 函数参数的可选类型
function tips(obj: { type: string; code?: number }) {
  console.log(obj.type, obj.code);
}
tips({ type: 'warning' });
tips({ type: 'success', code: 200 }); // code为可选参数

3.10 联合类型 |

让变量可能拥有联合类型中的一种。

function printId(id: string | number) {
  // console.log(id.toUpperCase()); // 报错,number类型上没有toUpperCase方法
  if (typeof id === 'string') {
    // 缩小类型
    console.log(id.toUpperCase());
  } else {
    console.log(id);
  }
}

printId(123);
printId('a001');
// printId(true);  // no

可选类型其实也会被推导成联合类型。

// a的类型为: number | undefined
function test(a?: number) {
console.log(100, a);
}

test();
test(100);

3.11 类型别名 type

类型别名可以将一系列相同的类型声明统合到一处。(类似声明一个变量一样声明一个类型,然后可以在多处使用)

// 类型别名
type IDType = string | number;
type UserInfoType = {
  name: string;
  age: number;
  height?: string | number;
  isStudent?: boolean;
};

const id: IDType = '0111';
const xiaoming: UserInfoType = {
  name: 'xiaoming',
  age: 18
};
function printUser(userinfo: UserInfoType) {
  console.log(`My name is ${userinfo.name}, i am ${userinfo.age} years old.`);
}
console.log(id);
printUser(xiaoming);

3.12 类型断言

有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。

通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。

类型断言有两种形式。

  • “尖括号”语法:
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;

注意:当你在TypeScript里使用JSX时,只有 as语法断言是被允许的。

获取dom元素的类型断言Property 'style' does not exist on type 'Element' in TS

TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换:

const name = 'fct' as number	// 报错
const name = ('fct' as unknown) as number

3.13 非空类型断言!

当我们编写下面的代码时,在执行ts的编译阶段会报错:

这是因为传入的message有可能是为undefined的,这个时候是不能执行方法的;

function print(msg?: string) {
    // 报错: msg可能是undefined
    console.log(msg.toUpperCase());
}

但是,我们确定传入的参数是有值的,这个时候我们可以使用非空类型断言:

非空断言使用的是 ,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测;

function print(msg?: string) {
    console.log(msg!.toUpperCase());
}

3.14 字面量类型

const message = 'hello, world';
let info = 'hello, world';

// 上述两种声明,ts推导的类型是不一样的
// message被推导为 const message: "hello, world"
// info类型被推导为 let info: string
// message的类型就被称为字面量类型,因为是const定义的,变量值是无法改变的。
// 同样,我们也可以使用let 来定义字面量类型(配合联合类型使用最佳)
type alignType = 'center' | 'left' | 'right';
let align1: alignType = 'center';
let align2: alignType = 'left';
let align3: alignType = 'right';

// let align: alignType = 'top'; // 报错,top不在值范围内

二、语法

1. 类型缩小

我们可以通过类似于 typeof padding === "number" 的判断语句,来改变TypeScript的执行路径,在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为 缩小;

而我们编写的 typeof padding === "number" 可以称之为 类型保护

类型包含有如下几种:

  • typeof
  • 平等缩小(=、!)
  • instanceof
  • in

1.1 typeof

function printId(id: string | number) {
  // console.log(id.toUpperCase()); // 报错,number类型上没有toUpperCase方法
  if (typeof id === 'string') {
    // 缩小类型
    console.log(id.toUpperCase());
  } else {
    console.log(id);
  }
}

1.2 平等缩小

function printDir(direction: "left" | "right") {
  if (direction === 'left') {
    // 缩小类型,方向为左就 +1
    return +1;
  }
  return -1;
}

1.3 instanceof

function printDateString(time: string | Date) {
  if (time instanceof Date) {
    // 缩小类型
    console.log(time.toUTCString());
  } else {
    console.log(time);
  }
}

1.4 in

// in
type Dog = {
  name: string;
  running: () => void;
};
type Fish = {
  name: string;
  swimming: () => void;
};

const dog: Dog = {
  name: 'dog',
  running() {
    console.log('running');
  }
};
const fish = {
  name: 'fish',
  swimming() {
    console.log('swimming');
  }
};

function action(animal: Dog | Fish) {
  if ('running' in animal) {
    animal.running();
  } else {
    animal.swimming();
  }
}

2. TypeScripts中的函数

当前函数无返回值,无需传参:() => void;

void 代表无返回值,而如需定义参数类型可在()中定义。

2.1 函数的类型

// 1. 函数作为类型参数
function fn() {
  console.log('fn');
}
// function sum(fn: Function) {
function sum(fn: () => void) {
  fn();
}
sum(fn);

// 2. 定义常量时指向函数
const addFun: (n1: number, n2: number) => number = function (num1: number, num2: number) {
  return num1 + num2;
};

console.log(addFun(1, 2));

2.2 函数参数的可选类型

✨可选类型一定放在必传类型后面。

function foo(x: number, y?: number) {
// function foo(x?: number, y: number) { // 必选参数不能位于可选参数后。
  console.log(x, y);
}

2.3 默认参数

从ES6开始,JavaScript是支持默认参数的,TypeScript也是支持默认参数的:

// y为number和undefined的联合类型
function bar(x: number, y: number = 6) {
  console.log(x, y);
}
bar(10);
bar(2, undefined);

// x为number和undefined的联合类型
function foo1(x: number = 1, y: number) {
  console.log(x, y);
}
foo1(undefined, 4);

注意:函数参数类型顺序可按:必传参数-->有默认值参数-->可选参数。

2.4 剩余参数

剩余参数可将函数接收的参数转换为相应的数组,作为函数参数列表的最后一项(不能在剩余参数后再添加函数参数)。

function foo(...nums: number[]) {
  return nums.reduce((pre, cur) => {
    return pre + cur;
  }, 0);
}

console.log(foo(1, 2, 3, 4, 5));

console.log(foo(4, 2, 3));

2.5 ts中的this问题

2.5.1 可推导的this类型

const obj = {
  name: 'fct',
  sayHello() {
    console.log(this.name);
  }
};
obj.sayHello(); // 可自动推断this

2.5.2 不确定的this类型

function eat() {
  console.log(this.food);
}
const foodObj = {
  food: 'fish',
  eat
};

// 'this'隐式具有类型'any',因为它没有类型注释
foodObj.eat(); // 'this' implicitly has type 'any' because it does not have a type annotation

2.5.3 指定this类型

type foodThis = { food: string };
function eat(this: foodThis, msg?: string) {
  console.log(this.food, msg);
}
const foodObj = {
  food: 'fish',
  eat
};
foodObj.eat(); // fish undefined
foodObj.eat('msg'); // fish msg
foodObj.eat.call({ food: 'orange' }, 'fct'); // orange fct
foodObj.eat.apply({ food: 'apple' }, ['xtt']); // apple xtt

2.6 函数的重载

编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用。

能使用联合类型简单实现的,优先使用联合类型。

// 使用联合类型无法实现
// function union(x:string | number, y: string|number) {
//   // return x + y; // no
// }

// 重载声明
function add(arg1: number, arg2: number): number;
function add(arg1: string, arg2: string): string;

// 不能直接调用add函数(传入参数为boolean等未声明值)
function add(arg1: any, arg2: any): any {
  if (typeof arg1 === 'number') {
    return arg1 + arg2;
  }
  return arg1.length + arg2.length;
}

console.log(add(1, 2));
console.log(add('abc', 'ff'));
// console.log(add(true, {name: 'fct'})); // no

3. 类的使用

3.1 简单使用

class Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  sayHello() {
    console.log(`hello,i am ${this.name},i am ${this.age} years old.`);
  }
}
const stu = new Person('fct', 18);
stu.sayHello();

3.2 继承

class Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  study() {
    console.log(`study`);
  }
}

class Student extends Person {
  grand: string;
  constructor(name: string, age: number, grand: string) {
    super(name, age);
    this.grand = grand;
  }
  goShcool() {
    console.log('去上学');
  }
  // 重写父类的方法
  study() {
    console.log('学生去学习');
  }
}
const xm = new Student('xiaoming', 12, '五年级');
console.log(xm.name);
console.log(xm.age);
console.log(xm.grand);
xm.study(); // 若子类有覆盖父类的方法,则调用子类的同名函数
xm.goShcool();

3.3 多态

class Animal {
  action() {
    console.log("animal action")
  }
}

class Dog extends Animal {
  action() {
    console.log("dog running!!!")
  }
}

class Fish extends Animal {
  action() {
    console.log("fish swimming")
  }
}

class Person extends Animal {

}

// animal: dog/fish/person
// 多态的目的是为了写出更加具备通用性的代码
function makeActions(animals: Animal[]) {
  animals.forEach(animal => {
    animal.action()
  })
}

makeActions([new Dog(), new Fish(), new Person()])

3.4 类的成员修饰符

在TypeScript中,类的属性和方法支持三种修饰符: public、private、protected:

  • public 修饰的是在任何地方可见、公有的属性或方法,默认编写的属性就是public的;
  • private 修饰的是仅在同一类中可见、私有的属性或方法;
  • protected 修饰的是仅在类自身及子类中可见、受保护的属性或方法;

3.4.1 public

本节之前写的内容均为默认public修饰符

3.4.2 private

class Person {
  // public age: number = 0;
  age: number = 0;
  private name: string = '';
  protected hobby: string = 'swimming';

  // 封装了两个方法, 通过方法来访问name
  getName() {
    return this.name;
  }

  setName(newName: string) {
    this.name = newName;
  }
}
class Stu extends Person {
  getHobby() {
    return this.hobby;
  }
}

const p = new Person();
const s = new Stu();
console.log(p.age);
console.log(s.getHobby());
// console.log(p.hobby); // 属性“hobby”受保护,只能在类“Person”及其子类中访问。
// console.log(p.name);  // 属性“name”为私有属性,只能在类“Person”中访问。
console.log(p.getName());
p.setName('fct');
console.log(p.getName());

3.5 只读属性readonly

如果有一个属性我们不希望外界可以任意的修改,只希望确定值后直接使用,那么可以使用readonly。

class Person {
  // 1.只读属性是可以在构造器中赋值, 赋值之后就不可以修改
  readonly name: string;
  age?: number;
  // 2.属性本身不能进行修改, 但是如果它是对象类型, 对象中的属性是可以修改
  readonly friend?: Person;
  constructor(name: string, friend?: Person) {
    this.name = name;
    this.friend = friend;
  }
}

const p = new Person('fct', new Person('wyt'));
console.log(p.name);
console.log(p.friend);

// 不可以直接修改friend
// p.friend = new Person("james")
if (p.friend) {
  p.friend.age = 30;
}

// p.name = "123";  // 无法分配到 "name" ,因为它是只读属性。
console.log(p);

3.6 getters/setters

在前面一些私有属性(private)我们是不能直接访问的,或者某些属性我们想要监听它的获取(getter)和设置(setter)的过程, 这个时候我们可以使用存取器。

class Person {
  private _name: string
  constructor(name: string) {
    this._name = name
  }

  // 访问器setter/getter
  // setter
  set name(newName) {
    this._name = newName
  }
  // getter
  get name() {
    return this._name
  }
}

const p = new Person("fct")
console.log(p.name)
p.name = "yyds"
console.log(p.name)

3.7 静态成员

之前在类中定义的成员和方法都属于对象级别的(即那些属性和方法只能通过new来创建对象,以获取),而在开发中同时也需要定义类成员及方法(直接通过类可以使用)。通过关键字static来定义:

class Student {
  static time: string = "09:00"

  static attendClass() {
    console.log("去学习~")
  }
}

console.log(Student.time)
Student.attendClass()

3.8 抽象类abstract

继承是多态使用的前提:

  • 在定义很多通用的调用接口时, 我们通常会让调用者传入父类,通过多态来实现更加灵活的调用方式。
  • 但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,,我们可以定义为抽象方法。

**抽象方法: **没有具体实现的方法(没有方法体),就是抽象方法。

  • 抽象方法,必须存在于抽象类中;
  • 抽象类是使用abstract声明的类;

抽象类:

  • 抽象类是不能被实例的话(也就是不能通过new创建)
  • 抽象方法必须被子类实现,否则该类必须是一个抽象类;
// 获取面积
function makeArea(shape: Shape) {
  return shape.getArea();
}

// 抽象类
abstract class Shape {
  // 抽象方法
  abstract getArea(): number;
}

class Rectangle extends Shape {
  private width: number;
  private height: number;

  constructor(width: number, height: number) {
    super();
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Circle extends Shape {
  private r: number;

  constructor(r: number) {
    super();
    this.r = r;
  }

  getArea() {
    return this.r * this.r * 3.14;
  }
}

const rectangle = new Rectangle(5, 10);
const circle = new Circle(10);

console.log(makeArea(rectangle));
console.log(makeArea(circle));
// 抽象类不能实例化
// makeArea(new Shape())

// 约束函数的参数类型为Shape
// makeArea(123)
// makeArea("123")

3.9 类的类型

类本身也是可以作为一种数据类型的:

class Person {
  name: string = '123';
  eating() {}
}

const p = new Person();

// 为p1指定为Person类型,则必须包含Person类定义的属性及方法
const p1: Person = {
  name: 'why',
  eating() {}
};

// 指定函数参数类型为Person类类型
function printPerson(p: Person) {
  console.log(p.name);
}

printPerson(new Person());
// 符合Person类的对象也可以作为函数参数
printPerson({ name: 'fct', eating: function () {} });
// 类型“Date”的参数不能赋给类型“Person”的参数。
// 类型“Date”缺少类型“Person”中的以下属性: name, eating
// printPerson(new Date());

4. 接口 Interface 的使用

我们可以通过type可以用来声明一个对象类型:

type Point = {
x: number;
y: number;
};

同样的,我们还可以使用接口来定义:

type Point = {
  readonly x: number;
  y: number;
  desc?: () => string;
  friends?: {
    name: string;
  };
};
interface IPonit {
  x: number;
  readonly y: number;
  desc?: () => number;
}

const p1: Point = {
  x: 0,
  y: 1,
  friends: {
    name: 'fct'
  }
};
// 无法为“x”赋值,因为它是只读属性。
// p1.x = 3;
// 可选链式调用
console.log(p1.friends?.name);

const p2: IPonit = {
  x: 6,
  y: 2,
  desc() {
    return this.x + this.y;
  }
};
// 无法为“y”赋值,因为它是只读属性。
// p2.y = 3;

// 非空类型断言
console.log(p2.desc!());

4.1 索引类型

通过interface定义索引类型

interface ILanguageObj {
  [index: number]: string;
}

const language: ILanguageObj = {
  0: 'C++',
  1: 'Java',
  2: 'JavaScript'
};

interface ILanguage {
  [sequence: string]: string;
}
const other: ILanguage = {
  one: 'C++',
  two: 'Java',
  three: 'JavaScript'
};

4.2 函数类型

前面我们都是通过interface来定义对象中普通的属性和方法的,实际上它也可以用来定义函数类型:

// 使用类型别名
// type CalcFn = (n1: number, n2: number) => number

// 使用接口定义函数
interface CalcFn {
  (n1: number, n2: number): number;
}

function calc(num1: number, num2: number, calcFn: CalcFn) {
  return calcFn(num1, num2);
}

const add: CalcFn = (num1, num2) => {
  return num1 + num2;
};

console.log(calc(20, 30, add));
console.log(add(1, 3));

使用接口定义函数类型,有些奇怪,除非特别的情况,还是推荐大家使用类型别名来定义函数。

type CalcFn = (n1: number, n2: number) => number

4.3 接口继承

接口和类一样是可以进行继承的,也是使用extends关键字。并且接口支持多继承(类不支持多继承)。

interface ISwim {
  // 游
  swimming: () => void;
}

interface IFly {
  // 飞
  flying: () => void;
}
// 多继承
interface IAction extends ISwim, IFly {
  // 呼吸
  breathe: () => void;
}

const action: IAction = {
  breathe() {},
  swimming() {},
  flying() {}
};

4.4 交叉类型 &

前面介绍了联合类型:联合类型表示多个类型中一个即可

type Direction = "left" | "right" | "center";

另外一种类型合并,就是交叉类型:

  • 交叉类似表示需要满足多个类型的条件;
  • 交叉类型使用&符号;

4.4.1 举例

type neverType = number & string;

表达的含义是numberstring要同时满足;但满足是一个number又是一个string的值其实是没有的,所以MyType其实是一个never类型;

使用交叉类型一般使用在在对象上。

4.4.2 简单使用

interface ISwim {
  swimming: () => void;
}

interface IFly {
  flying: () => void;
}

type MyType1 = ISwim | IFly;
type MyType2 = ISwim & IFly;

const obj1: MyType1 = {
  flying() {}
};
const obj2: MyType1 = {
  swimming() {}
};
const obj3: MyType1 = {
  flying() {},
  swimming() {}
};

const obj4: MyType2 = {
  swimming() {},
  flying() {}
};
// 缺少属性 "swimming"
// const obj5: MyType2 = {
//   flying() {}
// };

4.5 接口的实现

接口定义后,也是可以被类实现的:

  • 如果被一个类实现,那么在之后需要传入接口的地方,都可以将这个类传入;

  • 这就是面向接口开发

  • 类实现了接口,也就是将该接口里的属性、方法都交由类来实现

  • 类可以实现多个接口,而只能单继承

implements接口的实现:

interface ISwim {
  swimming: () => void;
}

interface IEat {
  eating: () => void;
}

// 类实现接口
class Animal {}

// 继承: 只能实现单继承
// 实现: 实现接口, 类可以实现多个接口
class Fish extends Animal implements ISwim, IEat {
  swimming() {
    console.log('Fish Swmming');
  }

  eating() {
    console.log('Fish Eating');
  }
}

class Person implements ISwim {
  swimming() {
    console.log('Person Swimming');
  }
}

// 编写一些公共的API: 面向接口编程
function swimAction(swimable: ISwim) {
  swimable.swimming();
}

// 1.所有实现了接口的类对应的对象, 都是可以传入
swimAction(new Fish());
swimAction(new Person());

swimAction({
  swimming: function () {
    console.log('swimming');
  }
});

4.6 interface 和 type 的区别

  1. interface可以重复定义同名的接口,而type定义的是别名,所以不能重新

    // 标识符“Person”重复。
    type Person = {
      name: string
    }
    
    type Person = {
      age: number
    }
    
    // 可重复定义
    interface Animal {
      name: string;
    }
    interface Animal {
      age: number;
    }
    interface Animal {
      run: () => void;
    }
    
    const cat: Animal = {
      name: 'cat',
      age: 2,
      run() {
        console.log('跑');
      }
    };
    cat.run();
    
  2. interface不太适合定义非对象类型,如函数。这时候推荐使用type,这样更加直观

    // 使用类型别名
    type CalcFn = (n1: number, n2: number) => number
    
    // 使用接口定义函数
    interface CalcFn {
      (n1: number, n2: number): number;
    }
    

4.7 字面量赋值

interface IPerson {
  name: string;
  age: number;
  height: number;
}

const p: IPerson = {
  name: 'fct',
  age: 23,
  height: 180
  // address: '四川省' // 会报错
};

const info = {
  name: 'fct1',
  age: 23,
  height: 180,
  address: '四川省'
};
// 这样不会报错
// 通过一个字面量给另一个约束了的变量赋值的时候,在验证时,会进行freshness擦除操作(假删除非类型声明的参数),以此推断字面量的参数是否满足类型约束
const p2: IPerson = info;

console.log(p);
console.log(p2);

5. 枚举类型

枚举类型是将一组可能出现的值,一个个列举出来,定义在一个类型中。

枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型;

// 相当于我们之前介绍的联合类型
// type Direction = "left" | "Right" | "Top" | "Bottom"

enum Direction {
  LEFT,
  RIGHT,
  TOP,
  BOTTOM
}

function turnDirection(direction: Direction) {
  switch (direction) {
    case Direction.LEFT:
      console.log('向左');
      break;
    case Direction.RIGHT:
      console.log('向右');
      break;
    case Direction.TOP:
      console.log('向上');
      break;
    case Direction.BOTTOM:
      console.log('向下');
      break;
    default:
      // 必须全部枚举完,不然就会报错
      const foo: never = direction;
      break;
  }
}

turnDirection(Direction.LEFT);
turnDirection(Direction.RIGHT);
turnDirection(Direction.TOP);
turnDirection(Direction.BOTTOM);

5.1 枚举类型的值

枚举类型默认是有值的,比如上面的枚举,默认值是这样的:

enum Direction {
  LEFT = 0,
  RIGHT = 1,
  TOP = 2,
  BOTTOM = 3
}

我们可以赋值,数字或字符串,设置数字时,后续的枚举值即+1:

enum Direction {
  LEFT = 100,
  RIGHT,
  TOP,
  BOTTOM = 'TOP'
}

function turnDirection(direction: Direction) {
  console.log(direction);
  // 100
  // 101
  // 102
  // TOP
}

turnDirection(Direction.LEFT);
turnDirection(Direction.RIGHT);
turnDirection(Direction.TOP);
turnDirection(Direction.BOTTOM);

6. 泛型

6.1 认识泛型

软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型。

/**
 * 声明泛型函数
 * @param: param  T泛型
 */
function printParam<T>(param: T): T {
  return param;
}

// 1. 通过泛型推导类型
// function printParam<number>(param: number): number
console.log(printParam<number>(10)); // 10
console.log(printParam<string>('fct')); // fct
console.log(printParam<{ name: string }>({ name: 'fct' })); // { name: 'fct' }
console.log(printParam<string[]>(['fct'])); // [ 'fct' ]

// 2. 字面量类型推导
// function printParam<50>(param: 50): 50
console.log(printParam(50));
console.log(printParam('gogogo'));

6.2 泛型接收的类型参数

我们可以传入多个类型:

// 传入多个类型
function say<T, E>(num: T, ele: E) {
  console.log(num, ele);
}
// function say<number, string>(num: number, ele: string): void
say(1, '222');

function bar<T, E, O>(n: T, e: E, ...args: O[]) {
  console.log(n, e, args);
}

bar<number, boolean, string>(1, true, 'a', 'b');

时在开发中我们可能会看到一些常用的名称:

  • T:Type的缩写,类型
  • K、V:key和value的缩写,键值对
  • E:Element的缩写,元素
  • O:Object的缩写,对象

6.3 泛型接口

在定义接口的时候我们也可以使用泛型:

// 泛型接口
interface IPerson<T1, T2> {
  name: T1;
  age: T2;
}
// 泛型类型“IPerson<T1, T2>”需要 2 个类型参数。
// const fct: IPerson = {};
const fct: IPerson<string, number> = {
  name: 'fct',
  age: 23
};

// 泛型类型的默认值
interface IPerson1<T1 = string, T2 = number> {
  name: T1;
  age: T2;
}
//默认泛型类型
const fct1: IPerson1 = {
  name: 'fct',
  age: 23
};

6.4 泛型类

同理,我们也可以编写泛型类,原生数组类对象其实也是使用的泛型类。

class Point<T> {
  x: T;
  y: T;
  z: T;

  constructor(x: T, y: T, z: T) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
}

// 实例化对象
const p1 = new Point('1.33.2', '2.22.3', '4.22.1');
const p2 = new Point<string>('1.33.2', '2.22.3', '4.22.1');
const p3: Point<string> = new Point('1.33.2', '2.22.3', '4.22.1');

// 数组申明
const names1: string[] = ['abc', 'cba', 'nba'];
const names2: Array<string> = ['abc', 'cba', 'nba']; // 不推荐(react jsx <>)

6.5 泛型的类型约束

用来限制T

有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中:

  • 比如string和array都是有length的,或者某些对象也是会有length属性的;
  • 那么只要是拥有length的属性都可以作为我们的参数类型
interface ILength {
  length: number;
}

function getLength<T extends ILength>(args: T) {
  return args.length;
}

// getLength(123); // 类型“number”的参数不能赋给类型“ILength”的参数
console.log(getLength('123'));
console.log(getLength(['123', '321']));
// getLength({}); // 缺少属性 "length"
console.log(getLength({ length: 6 }));

7. 模块化开发

7.1 模块化

模块化:每个文件可以是一个独立的模块,支持ES Module,也支持CommonJS;

./utils/math.ts

export function add(num1: number, num2: number) {
  return num1 + num2;
}

export function subtract(num1: number, num2: number) {
  return num1 - num2;
}

main.ts

// ESModule 模块化
import { add, subtract } from './utils/math';

console.log(add(2, 3));
console.log(subtract(5, 6));

7.2 命名空间

通过namespace来声明一个命名空间

./utils/format.ts

namespace Time {
  // 内部的方法
  const _name = 'fct';
  // 导出的方法
  export function format(param: string) {
    return '2023-01-04';
  }
}

namespace Num {
  const _num = 666;
  export function format(param: string) {
    return _num;
  }
}

export { Time, Num };

main.ts

// 命名空间
import { Time, Num } from './utils/format';

console.log(Time.format('aa'));
console.log(Num.format('aa'));

7.3 类型的查找

之前我们所有的typescript中的类型,几乎都是我们自己编写的,但是我们也有用到一些其他的类型:

  • const img = document.getElementById('image') as HTMLImageElement;
    

    这里用到了HTMLImageElement类型,那这个类型我们并没有申明过,并且document上为什么会有getElementById方法?

    这里就涉及到typescript对类型的管理和查找规则

  • typescript文件格式还有:.d.ts文件

    • 之前编写的typescript文件都是 .ts 文件,这些文件最终会输出 .js 文件,也是我们通常编写代码的地方;
    • 还有另外一种文件 .d.ts 文件,它是用来做类型的声明(declare)。 它仅仅用来做类型检测,告知typescript我们有哪些类型;
  • typescript会以下地方查找我们的类型声明:

    • 内置类型声明;
    • 外部定义类型声明;
    • 自己定义类型声明;

7.3.1 内置类型声明

内置类型声明是typescript自带的、帮助我们内置了JavaScript运行时的一些标准化API的声明文件,包括但不限于如:Math、Date等内置类型,也包括DOM API,比如Window、Document等;

内置类型声明通常在我们安装typescript的环境中会带有的。TypeScript/lib at main · microsoft/TypeScript · GitHub

7.3.2 外部定义类型声明

外部类型声明通常是我们使用一些库(比如第三方库)时,需要的一些类型声明。

这些库通常有两种类型声明方式:

  • 第三方库中已经进行类型过声明(编写了.d.ts文件),比如axios;

    • import axios from "axios";
      
      // 无法找到模块“lodash”的声明文件
      // import lodash from "lodash";
      
  • 若第三方库未进行类型申明,则可以通过社区的一个公有库DefinitelyTyped存放类型声明文件

7.3.3 自己定义类型声明

当在公有库DefinitelyTyped中未查询到相应的类型声明文件时,我们需要自己来定义声明文件,又或者我们给自己的代码中声明一些类型,方便在其他地方直接进行使用;

// 声明模块(处理import导入报错)
// lodash还可以自己来声明(但不建议,因为使用的所有方法都得定义且没有好的代码提示)
declare module 'customize' {
  // 导出模块的方法
  export function join(arr: any[]): void
}

// 声明变量/函数/类
declare let userName: string
declare let userAge: number
declare let userHeight: number

// 声明函数
declare function whyFoo(): void

// 声明类
declare class Person {
  name: string
  age: number
  constructor(name: string, age: number)
}

// 声明文件
// import导入图片时报错使用
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.png'
declare module '*.svg'
declare module '*.gif'

// 声明命名空间
// 若使用了jQuery,则可以声明命名空间,在代码里直接使用
declare namespace $ {
  export function ajax(settings: any): any
}
posted @ 2023-01-05 15:19  青柠i  阅读(67)  评论(0编辑  收藏  举报