玩命加载中🤣🤣🤣

TS速通笔记


TS速通笔记

编译 TypeScript

1.命令行编译

要把 .ts 文件编译为 .js 文件,需要配置 TypeScript 的编译环境

  1. 创建一个 demo.ts
const person = {
  name: '张三',
  age: 18,
}
console.log(`姓名:${person.name},年龄:${person.age}`)
  1. 安装 typescript
npm i -g typescript
  1. 编译
tsc demo.tx

2.自动化编译

# 在文件夹下执行
tsc --init
# 监视文件
tsc --watch demo.tx # 或tsc -w

# 监视当前工作目录
tsc --watch

init 后会生成 tsconfig.json 文件,在这里可以设置编译 JavaScript 的版本、严格模式等,同时建议打开配置文件中的 noEmitOnError,避免后续出现错误的代码也被编译为 .js 文件。

tsconfig.json 配置部分说明

  • target:转换目标语法,可以选择 es6
  • noEmitOnError:当出现错误时不要提交
  • 一般使用脚手架 Webpack 和 Vite,他会有自己的方式

类型声明

TypeScript使⽤ : 来对变量或函数形参,进⾏类型声明:

let a: string // 变量a只能存储字符串类型,不能是包装类,TS官方推荐写法
let b: number // 变量b只能存储数值类型
let c: boolean // 变量c只能存储布尔值

a = 'hello'
b = -10
c = true

// 参数x必须是数字类型,参数y也必须是数字类型,函数返回值也必须是数字类型
// 参数只能是2个,不能是1个或者3个
function count(x: number, y: number): number {
  return x + y
}

let result = count(10, 20)
console.log(result)

数据类型

类型总览

  • JavaScript 中的数据类型
number
string
boolean
undefined
null
bigint
symbol
object
备注:其中 object 包含: Array、Function、Date、Error 等...
  • TypeScript 中的数据类型
    • JavaScript中的8个数据类型
    • 6个新的数据类型
    • 2个用于自定义类型的方式:① type ② interface

常用类型

  • any:摆烂型数据,不建议用
  • unknow:可以使用,不会污染别的数据
let a: unknown

a = 99
a = false
a = 'hello'

console.log(a)

let x: string

// 第一种
if (typeof a === 'string') {
  x = a
}

// 第二种(断言)
x = a as string

// 第三种(断言)
x = <string>a
let str1: unknown
str1 = 'hello';

// 类型转换
(str1 as string).toUpperCase()
  • never: 不用管

  • void:通常用于返回值,undefined

  • object/Object:用的很少

声明对象类型

实际开发中通常使用以下

// 用 ,
let person1: { name: string, age?: number }
// 用 ;
let person2: { name: string; age?: number }

// 用 换行
let person3: {
	name: string
	age?: number  // 加?代表可以为空
}

// 如下赋值均可以
person1 = {name:'李四',age:18}
person2 = {name:'张三'}
person3 = {name:'王五'}

索引签名

let person: { 
	name: string
	age?: number
	[key: string]: any
}

// 赋值合法
person = { 
	name:'张三', 
	age:18, 
	gender:'男' 
}

声明函数类型

let count: (a: number, b: number) => number
count = function (x, y) { return x + y }

声明数组

let arr1: string[]
let arr2: Array<string>
	
arr1 = ['a','b','c']
arr2 = ['hello','world']

tuple

let arr1: [string,number]
let arr2: [number,boolean?]
let arr3: [number,...string[]]

// 可以赋值
arr1 = ['hello',123]
arr2 = [100,false]
arr2 = [200]
arr3 = [100,'hello','world']
arr3 = [100]

枚举

数字枚举

enum Direction {
    Up,
    Down,
    Left,
    Right
}
console.log(Direction);
// 反向映射
console.log(Direction.Up);
console.log(Direction[0]);

字符串枚举

enum Direction {
	Up = "up",
	Down = "down",
	Left = "left",
	Right = "right"
}
let dir: Direction = Direction.Up;
console.log(dir); // 输出: "up"

常量枚举

const enum|Directions {
	Up,
	Down,
	Left,
	Right
}

let x = Directions.Up;

编译之后生成的 JavaScript

"use strict";
let x = 0 /* Directions.Up */;

type

联合类型

type Status = number | string;
type Gender = '男' | '女';
// 限制
function printStatus(status: Status) {
	console.log(status);
}
function logGender(str: Gender) {
	console.log(str);
}
// 调用函数
printStatus(404);
printStatus('200');
printStatus('501');

logGender('男');
logGender('女');

交叉类型

//⾯积
type Area = {
	height: number; //⾼
	width: number; //宽
};

//地址
type Address = {
	num: number; //楼号
	cell: number; //单元号
	room: string; //房间号
};

// 定义类型House,且House是Area和Address组成的交叉类型
type House = Area & Address;
// 赋值
const house: House = {
	height: 180,
	width: 75,
	num: 6,
	cell: 3,
	room: '702'
};

定义函数与特殊情况

// 函数类型声明
type LogFunc = () => void;

// 定义函数
const f1: LogFunc = () => {
  console.log("hello");
  return 999; // 不报错
};

// 这里出现了特殊情况:我在声明了返回值为void,但是实际返回了 999,结果没有报错

官方解释,是为了如下代码成立:

const src = [1, 2, 3];
const dst = [0];
// foreach 的定义中:返回值是 void,但是这个案例中,通过 dst.push(e1) 是有返回值的
src.forEach((e1) => dst.push(e1));

再比如:

// 定义函数类型
type LogFunc = () => void;

const f1: LogFunc = () => {
  return 999;
};

let x = f1();

console.log(x); // 虽然可以打印,但是没办法做后续操作

官⽅⽂档的说明:Assignability of Functions

关注如下几个点:

  • 类定义
    • 属性定义
    • 构造器
  • 类继承
    • 新增属性
    • 重写方法
// 类定义
class Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  say() {
    console.log(`我是${this.name}, 我今年${this.age}`);
  }
}

// 实例化
const p1 = new Person("张三", 18);
p1.say();

// 类继承
class Student extends Person {
  // 新增属性
  grade: string;
  constructor(name: string, age: number, grade: string) {
    // 调用父类的构造函数
    super(name, age);
    // 初始化新增属性
    this.grade = grade;
  }
  // 重写方法
  override say() {
    console.log(`我是${this.name}, 我今年${this.age}岁, 我在读${this.grade}`);
  }
  // 新增方法
  study() {
    console.log(`${this.name}正在学习`);
  }
}

const s1 = new Student("李四", 18, "三年级");
s1.say();
s1.study();

属性修饰符

修饰符 含义 具体规则
public 公开的 可以被:类内部、子类、类外部访问。
protected 受保护的 可以被: 类内部、子类访问。
private 私有的 可以被: 类内部访问。
readonly 只读属性 属性无法修改。
  • public 可以用作类的简写
// 完整
class Person {
  public name: string;
  public age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

// 简写
class Person {
  constructor(
    public name: string,
    public age: number
  ) {}
}

抽象类

抽象类示例

// 抽象类
abstract class Package {
  // 构造器(简略写法)
  constructor(public weight: number) {}
  // 抽象方法
  abstract calculate(): number;
  // 具体方法
  printPackage() {
    return `包裹重量为: ${this.weight}kg, 运费为: ${this.calculate()}`;
  }
}

class StandardPackage extends Package {
  // 构造器 注意:这里重量是继承的,简写方式可以省略 publicc,但是 unitPrice 是新增的属性,不能省略 public
  constructor(weight: number, public unitPrice: number) {
    // 调用父类的构造器
    super(weight);
  }
  // 实现抽象方法
  calculate(): number {
    return this.weight * this.unitPrice;
  }
}

const p1 = new StandardPackage(10, 100);
console.log(p1.printPackage());

控制台

包裹重量为: 10kg, 运费为: 1000元

总结:何时使用抽象类

  1. 定义通用接口 :为⼀组相关的类定义通⽤的⾏为(⽅法或属性)时。
  2. 提供基础实现:在抽象类中提供某些⽅法或为其提供基础实现,这样派⽣类就可以继承这 些实现。
  3. 确保关键实现:强制派⽣类实现⼀些关键⾏为。
  4. 共享代码和逻辑:当多个类需要共享部分代码时,抽象类可以避免代码重复。

interface

定义类结构

// 接口定义类
interface IPerson {
  name: string;
  age: number;
  speak(n: number): void;
}

class Person implements IPerson {
  constructor(public name: string, public age: number) {}
  speak(n: number): void {
    for (let i = 0; i < n; i++) {
      console.log(`我是${this.name}, 我今年${this.age}`);
    }
  }
}

const p1 = new Person('张三', 18);
p1.speak(3);

控制台输出

我是张三, 我今年18岁
我是张三, 我今年18岁
我是张三, 我今年18岁

定义对象结构

// 接口定义对象
interface IUser {
  name: string;
  readonly gender: string;
  age?: number;
  run: (n: number) => void;
}

const user: IUser = {
  name: "张三",
  gender: "男",
  age: 18,
  run: (n: number) => {
    console.log(`我是${user.name}, 我今年${user.age}岁, 奔跑了${n}`);
  }
}

user.run(10);

用接口限制函数

// 接口定义函数
interface ICount {
  (a: number, b: number): number;
}

const count: ICount = (a, b) => {
  return a + b;
}

接口可以继承

// 接口之间的继承
interface IPerson {
  name: string;
  age: number;
}

interface IStudent extends IPerson {
  grade: string;
}

const stu: IStudent = {
  name: "张三",
  age: 18,
  grade: "一年级",
}

接口自动合并(可重复定义)

// 接口自动合并
interface IPerson {
  name: string;
  age: number;
}

interface IPerson {
  gender: string;
}

const p: IPerson = {
  name: "张三",
  age: 18,
  gender: "男",
}

接口总结:何时使用接口?

  1. 定义对象的格式描述数据模型API 响应格式配置对象…等等,是开发中⽤的最多的场景。
  2. 类的契约:规定⼀个类需要实现哪些属性和⽅法。
  3. 自动合并:⼀般⽤于扩展第三⽅库的类型, 这种特性在⼤型项⽬中可能会⽤到。

一些相似概念的区别

interface与type的区别

  • 相同点:interfacetype 都可以定义对象结构,两者在许多场景中可以互换
  • 不同点:
    • interface:更专注于定义对象的结构,支持继承、合并
    • type:可以定义类型别名、联合类型、交叉类型,但不支持继承和自动合并

代码示例两者的互换

定义对象结构

// 使用 interface 定义 Person 对象
interface IPerson {
  name: string;
  age: number;
  speak(): void;
}

// 使用 type 定义 Person 对象
type TPerson = {
  name: string;
  age: number;
  speak(): void;
}

继承与合并

// 使用 interface 定义 Person 对象
interface IPerson {
  name: string;
  age: number;
}

interface IPerson {
  speak(): void;
}

// 使用 interface 定义 Student 类型,并通过继承 IPerson 接口
interface IStudent extends IPerson {
  grade: string;
}

const stu: IStudent = {
  name: "张三",
  age: 18,
  grade: "一年级",
  speak() {
    console.log("我是一个学生");
  },
};

// 使用 type 定义 Person 对象
type TPerson = {
  name: string;
  age: number;
} & {
  speak(): void;
};

// 使用 type 定义 Student 类型,并通过交叉类型继承 PersonType
type TStudent = TPerson & {
  grade: string;
};

const stu2: TStudent = {
  name: "李四",
  age: 20,
  grade: "二年级",
  speak() {
    console.log("我是一个学生");
  },
};

interface与抽象类的区别

  • 相同点:都能定义⼀个类的格式(定义类应遵循的契约)
  • 不同点:
    • 接⼝:只能描述结构不能有任何实现代码,⼀个类可以实现多个接⼝。
    • 抽象类:既可以包含抽象⽅法,也可以包含具体⽅法,⼀个类只能继承⼀个抽象类。
// 接口
interface IFly {
  fly(): void;
}

interface ISwim {
  swim(): void;
}

class Duck implements IFly, ISwim {
  fly() {
    console.log("我会飞");
  }
  swim() {
    console.log("我会游泳");
  }
}

泛型

泛型函数

// 泛型
function logData<T>(data: T): void {
  console.log(data);
}

logData<string>("hello");
logData<number>(123);

泛型接口

interface IPerson<T> {
  name: string;
  age: number;
  extraInfo: T;
}

type JobInfo = {
  title: string;
  company: string;
}

let p: IPerson<JobInfo> = {
  name: "张三",
  age: 18,
  extraInfo: {
    title: "前端开发",
    company: "公司A",
  },
};

类型声明文件

类型声明⽂件是 TypeScript 中的⼀种特殊⽂件,通常以 .d.ts 作为扩展名。它的主要作⽤是为现有的 JavaScript 代码提供类型信息,使得 TypeScript 能够在使用这些 JavaScript 库或模块时进行类型检查和提示。

ts引用js所导致的问题:

先定义一个 demo1.js

export function add(a, b) {
  return a + b;
}
export function sub(a, b) {
  return a - b;
}

再 ts 中引用

import { add, sub } from "./demo1.js";

此时控制台报错

无法找到模块“./demo1.js”的声明文件。“xxx/ts-demo/demo1.js”隐式拥有 "any" 类型。ts(7016)

类型声明解决

定义一个 demo1.d.ts,有 2 种写法

declare module "./demo1.js" {
  export function add(a: number, b: number): number;
  export function sub(a: number, b: number): number;
}

写法2:

declare function add(a: number, b: number): number;
declare function sub(a: number, b: number): number;

export { add, sub };

这些文件一般不用自己写,老代码的组织会封装好。核心能力就是为现有的 js 代码提供类型信息,方便 ts 引用。


文章作者: 👑Dee👑
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 👑Dee👑 !
  目录