PL-TypeScript

学习自尚硅谷。

资源

正文

一、TypeScript 简介

webp
  1. TypeScript 由微软开发,是基于 Javascript 的一个扩展语言
  2. TypeScript 包含了 JavaScript 的所有内容,即:TypeScript 是 JavaScript 的超集
  3. Typescript 增加了:静态类型检查、接口、泛型等很多现代开发特性,因此更适合大型项目的开发。
  4. TypeScript 需要编译为 JavaScript ,然后交给浏览器或其他 JavaScript 运行环境执行。

二、为何需要 TypeScript

1. 今非昔比的 Javascript

  1. JavaScript 当年诞生时的定位是浏览器脚本语言,用于在网页中嵌入一些简单的逻辑,而且代码量很少。
  2. 随着时间的推移,JavaScript 变得越来越流行,如今的 JavaScript 已经可以全栈编程了。
  3. 现如今的 JavaScript 应用场景比当年丰富的多,代码量也比当年很多,随便一个JavaScript 项目的代码量,可以轻松的达到几万行,甚至十几万行!
  4. 然而 JavaScript 当年“出生简陋”,没考虑到如今的应用场景和代码量,逐渐的就出现了很多困扰

2. Javascript 中的困扰

1. 不清不楚的数据类型

js
let welcome ='hello'
welcome()  // 此行报错:TypeError: welcome is not a function

2. 有漏洞的逻辑

js
const str = Date.now() % 2 ? '奇数' : '偶数'
 
if (str !== '奇数') {
    alert('hello')
} else if (str === '偶数') {
    alert('world')  // 这行代码由于逻辑有漏洞,永远不会执行
}

3. 访问不存在的属性

js
const obj = {width : 10, height: 15};
const area = obj.width * obj.heigth;  // 拼写错误,在 js 上仍能执行

4. 低级的拼写错误

js
const message = 'hello, world!'
message.toUperCase()  // 拼写错误,在 js 上仍能执行

3. 静态类型检查

  • 在代码运行前进行检查,发现代码的错误或不合理之处,减小运行时异常的出现的几率,此种检查叫『静态类型检查』,TypeScript 和核心就是「静态类型检查』,简言之就是把运行时的错误前置
  • 同样的功能,TypeScript 的代码量要大于 JavaScript,但由于 TypeScript 的代码结构更加清晰,在后期代码的维护中 TypeScript 却远胜于 JavaScript。

三、编译 TypeScript

提示

浏览器不能直接运行 TypeScript 代码,需要编译为 JavaScript 再交由浏览器解析器执行。

1. 命令行编译(几乎不用)

要把 .ts 文件编译为 .js 文件,需要配置 Typescript 的编译环境,步骤如下:

  • 第一步:创建一个 demo.ts 文件,例如:
ts
const person = {
    name: '李四',
    age: 18
}
console.log(`我叫${person.name},我今年${person.age}岁了`)
  • **第二步:**全局安装 TypeScript
shell
npm i typescript -g
  • 第三步:使用命令编译 .ts 文件:
shell
tsc demo.ts

得到 js 文件:

js
var person = {
    name: '李四',
    age: 18
};
console.log("\u6211\u53EB".concat(person.name, "\uFF0C\u6211\u4ECA\u5E74").concat(person.age, "\u5C81\u4E86"));

2. 自动化编译

  • 第一步:项目中初始化 ts:
shell
tsc --init
 
Created a new tsconfig.json with:                                                                                       
                                                                                                                     TS 
  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
  skipLibCheck: true
  forceConsistentCasingInFileNames: true
 
 
You can learn more at https://aka.ms/tsconfig
  • 第二步:这将在项目工程中得到 tsconfig.json,进去把下面这段取消注释,以在 .ts 编写错误时不再自动编译。
json
"noEmitOnError": true,
  • 第三步:监视目录中的 .ts 变化:
shell
tsc --watch

如果第二步没有做,也可以这么处理:

shell
tsc --noEmitOnError --watch

四、类型声明

使用 : 来对变量或函数形参,进行类型声明:

ts
let a: string  //变量 b 只能存储数值
let b: number  //变量 c 只能存储布尔值
let c: boolean
 
a = 'hello'
a = 100  // 警告:不能将类型“number”分配给类型“string"
 
b = 666
b = '你好'  // 警告:不能将类型“string”分配给类型“number”
 
c = true
c = 666  // 警告:不能将类型“number”分配给类型“boolean
 
// 参数 x 必须是数字,参数 y 也必须是数字,函数返回值也必须是数字
function demo(x:number, y:number):number
{
    return x + y
}
 
demo(100, 200)
demo(100, '208')  //警告:类型“string”的参数不能赋给类型“number”的参数
dema(100,200,300)  //警告:应有 2 个参数,但获得 3 个
demo(100)  // 警告:应有 2 个参数,但获得 1 个

: 后也可以写字面量类型,不过实际开发中用的不多(感觉像 const)。

ts
let a:'你好’  // a 的值只能为字符串“你好
let b: 100  // b 的值只能为数字 100
a = '欢迎"  // 警告不能将类型“"欢迎"”分配给类型“"你好"
b = 200  // 警告:不能将类型“200”分配给类型“100”

五、类型推断

TS 会根据我们的代码,进行类型推导,例如下面代码中的变量 d,只能存储数字

ts
let d = -99  // Typescript 会推断出变量 d 的类型是数字
d = false  // 警告:不能将类型“boolean”分配给类型“number”

警告

但要注意,类型推断不是万能的,面对复杂类型时推断容易出问题,所以尽量还是明确的编写类型声明!

六、类型总览

JS 中的数据类型

  1. string
  2. number
  3. boolean
  4. null
  5. undefined
  6. bigint(大整型)
  7. symbol
  8. object

备注:其中 object 包含:ArrayFunctionDateError

TS 中的数据类型

  1. 上述所有 JS 类型
  2. 六个新类型:
    1. any
    2. unknown
    3. never
    4. void
    5. tuple
    6. enum
  3. 两个用于自定义类型的方式:
    1. type
    2. interface

注意点

重要

Javascript 中的这些内置构造函数:NumberStringBoolean 它们用于创建对应的包装对象,在日常开发时很少使用,在 TypeScript 中也是同理,所以在 Typescript 中进行类型声明时,通常都是用小写的 numberstringboolean

ts
let str1: string
str1 = 'hello'
str1 = new String('hello')  // 报错
 
let str2: String
str2 = 'hello'
str2 = new String('hello')
 
console.log(typeof str1)
console.log(typeof str2)
  1. 原始类型 VS 包装对象
    • 原始类型:如 numberstringboolean,在 JavaScript 中是简单数据类型,它们在内存中占用空间少,处理速度快。
    • 包装对象:如 Number 对象、String 对象、Boolean 对象,是复杂类型,在内存中占用更多空间,在日常开发时很少由开发人员自己创建包装对象。
  2. 自动装箱:JavaScript 在必要时会自动将原始类型包装成对象,以便调用方法或访问属性。
ts
// 原始类型字符串
let str ='hello";
 
// 当访问 str.length 时,JavaScript 引擎做了以下工作:
let size = (function(){
    // 1. 自动装箱:创建一个临时的 string 对象包装原始字符串
    let tempstringobject = new string(str);
    
	// 2. 访问 String 对象的 length 属性,由于 JS 会自动装箱,因此这个属性是可以访问到的
    let lengthValue = tempstringobject.length;
 
    // 3. 销毁临时对象,返回长度值
    // (Javascript 引擎自动处理对象销毁,开发者无感知)
    return lengthValue;
})();
 
console,log(size);  // 输出:5

七、常用类型

1. any

any 的含义是:任意类型,一旦将变量类型限制为 any,那就意味着放弃了对该变量的类型检查。

ts
// 明确的表示 a 的类型是 any - 【显式的 any】
let a: any
// 以下对 a 的赋值,均无警告
a = 100
a = '你
a = false
 
// 没有明确的表示 b 的类型是 any,但 TS 主动推断出来 b 是 any-隐式的 any
let b
// 以下对 b 的赋值,均无警告
b = 100
b ='你
b = false

注意点:any 类型的变量,可以赋值给任意类型的变量。

ts
/* 注意点:any 类型的变量,可以赋值给任意类型的变量 */
let c:any
c = 9
 
let x: string
x = c  // 无警告

2. unknown

unknown 的含义是:未知类型

  • unknown 可以理解为一个类型安全的 any,适用于:不确定数据的具体类型。
ts
// 设置 a 的类型为 unknown
let a: unknown
 
// 以下对 a 的赋值,均正常
a = 100
a = false
a ='你好'
 
// 设置 x 的数据类型为 string
let x: string
x =  a  // 警告:不能将类型“unknown”分配给类型“string”
  • unknown强制开发者在使用之前进行类型检查,从而提供更强的类型安全性。
ts
// 设置 a 的类型为 unknown
let a: unknown
a = 'hello'
 
// 第一种方式:加类型判断
if (typeof a ==='string') {
    x = a
	console.log(x)
}
 
// 第二种方式:加断言
x = a as string
 
// 第三种方式:加断言
x = <string>a
  • 读取 any 类型数据的任何属性都不会报错,而 unknown 正好与之相反。
ts
let str1: string
str1 ='hello'
str1.toUppercase()  // 无警告
 
let str2: any
str2 ='hello'
str2.toUppercase()  // 无警告
 
let str3: unknown
str3 = 'hello'
str3.toUppercase()  // 警告:“str3”的类型为“未知”
(str3 as string).toUpperCase()  // 要这么写

3. never

never 的含义是:任何值都不是,简言之就是不能有值,undefinednull''0 都不行!

  • 几乎不用 never 去直接限制变量,因为没有意义,例如:
ts
/* 指定 a 的类型为 never,那就意味着 a 以后不能存任何的数据了 */
let a: never
 
// 以下对 a 的所有赋值都会有警告
a = 1
a = true
a = undefined
a = null
  • never 一般是 TypeScript 主动推断出来的,例如:
ts
// 指定 a 的类型为 string
let a: string
// 给 a 设置一个值
a = 'hello'
 
if (typeof a ==='string') {
	console.log(a.toUppercase())
} else {
	console.log(a)  // TypeScript 会推断出此处的 a 是 never,因为没有任何一个值符合此处的逻辑
}
  • never 也可用于限制函数的返回值
ts
// 限制 throwError 函数不需要有任何返回值,任何值都不行,像 undeifned、null 都不行
function throwError(str: string): never {
	throw new Error('程序异常退出:'+ str)
}

4. void

  • void 通常用于函数返回值声明,含义:【函数不返回任何值,调用者也不应依赖其返回值进行任何操作】
ts
function logMessage(msg:string):void {
    console.log(msg)
}
logMessage('你好')

警告

编码者没有编写 return 去指定函数的返回值,所以 logMessage 函数是没有显式返回值的,但会有一个隐式返回值,就是 undefined,即:虽然函数返回类型为 void,但也是可以接受 undefined 的,简单记:undefinedvoid 可以接受的一种“空”。

  • 以下写法均符合规范
ts
// 无警告
function logMessage(msg:string):void {
    console.log(msg)
}
ts
// 无警告
function logMessage(msg:string):undefined {
    console.log(msg)
}
ts
let result = logMessage('你好')
 
if(result) {  // 此行无警告
    console.log('logMessage 有返回值')
}

理解 voidundefined

  • void 是一个广泛的概念,用来表达“空”,而 undefined 则是这种“空”的具体实现之一。
  • 因此可以说 undefinedvoid 能接受的“空”状态的一种具体形式。
  • 换句话说:void 包含 undefined,但 void 表达的语义超越了单纯的 undefined,它是一种意图上的约定,而不仅仅是特定值的限制。

总结:若函数返回类型为 void,那么:

  • 从语法上讲:函数是可以返回 undefined 的,至于显示返回,还是隐式返回,这无所谓!
  • 从语义上讲:函数调用者不应关心函数返回的值,也不应依赖返回值进行任何操作!即使返回了 undefined 值。

5. object

提示

关于 objectObject,直接说结论:实习开发中用的相对较少,因为范围太大了。

object(小写)

object(小写)的含义是:所有非原始类型,可存储:对象、函数、数组等,由于限制的范围比较宽泛,在实际开发中使用的相对较少

ts
let a:object  // a 的值可以是任何【非原始类型】,包括:对象、函数、数组等
// 以下代码,是将【非原始类型】赋给 a,所以均符合要求
a = {}
a = {name: '张三'}
a = [1, 3, 5, 7, 9]
a = function(){}
a = new string('123')
class Person {}
a = new Person()
 
// 以下代码,是将【原始类型】赋给 a,有警告
a = 1  // 警告:不能将类型“number”分配给类型“object”
a = true  // 警告:不能将类型“boolean”分配给类型“object”
a ='你好'  // 警告:不能将类型“string”分配给类型“object”
a = null  // 警告:不能将类型“null”分配给类型“object”
a = undefined // 警告:不能将类型“undefined”分配给类型“object”

Object(大写)

  • 官方描述:所有可以调用 Object 方法的类型。
  • 简单记忆:除了 undefinednull 的任何值。
  • 由于限制的范围实在太大了!所以实际开发中使用频率极低

声明对象类型

  • 实际开发中,限制一般对象,通常使用以下形式:
ts
// 限制 person1 对象必须有 name 属性,age 为可选属性(带 ? 符号都是可选)
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: '王五'}
 
//如下赋值不合法,因为 person3 的类型限制中,没有对 gender 属性的说明
person3 = {name: '王五', gender: '男'}

索引签名

  • 允许定义对象可以具有任意数量的属性,这些属性的类型可变的,常用于:描述类型不确定的属性,(具有动态属性的对象)。
ts
// 限制 person 对象必须有 name 属性,可选 age 属性但值必须是数字,同时可以有任意数量、任意类型
let person: {
	name: string,
	age?: number,
	[key:string]: any  // 索引签名,完全可以不用 key 这个单词,换成其他的也可以
}
 
// 赋值合法
person = {
	name:'张三',
	age:18,
	gender:'男'  // 后面随便定义
}

声明函数类型

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

提示

  • TypeScript 中的 => 在函数类型声明时表示函数类型,描述其参数类型返回类型
  • JavaScript 中的 => 是一种定义函数的语法,是具体的函数实现。和
  • 函数类型声明还可以使用:接口、白定义类型等方式。

声明数组类型

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

上述代码中的 Array<string> 属于泛型,下文会详细讲解。

6. tuple

注意

元组(Tuple)是一种特殊的数组类型,可以存储固定数量的元素,并且每个元素的类型是已知的可以不同。元组用于精确描述一组值的类型,? 表示可选元素。

ts
// 第一个元素必须是 string 类型,第二个元素必须是 number 类型。
let arr1: [string, number]
// 第一个元素必须是 number 类型,第二个元素是可选的,如果存在,必须是 boolean 类型。
let arr2: [number, boolean?]
// 第一个元素必须是 number 类型,后面的元素可以是任意数量的 string 类型。
let arr3: [number, ...string[]]
// 可以赋值
arr1 = ['hello', 123]
arr2 = [100, false]
arr2 = [200]
arr3 = [100, 'hello', 'world']
arr3 = [100]
// 不可以赋值,arr1 声明时是两个元素,赋值的是三个
arr1 = ['hello', 123, false]

7. enum

注意

枚举(enum)可以定义一组命名常量,它能增强代码的可读性,也让代码更好维护

如下代码的功能是:根据调用 walk 时传入的不同参数,执行不同的逻辑,存在的问题是调用 walk 时传参时没有任何提示,编码者很容易写错字符串内容;并且用于判断逻辑的 updownleftright连续且相关的一组值,那此时就特别适合使用枚举(enum)

数字枚举

{% tabs enum %}

ts
// 不使用枚举
function walk(str:string){
    if (str === 'up') {
        console.log("向【上】走");
    } else if (str === 'down') {
        console.log("向【下】走");
    } else if (str === 'left') {
        console.1og("向【左】走");
    } else if (str === 'right') {
        console.log("向【右】走");
    } else {
        console.log("未知方向");
    }
}
 
walk("up")
walk("down")
walk('left')
walk('right')

数字枚举一种最常见的枚举类型,其成员的值会自动递增,且数字枚举还具备反向映射的特点,在下面代码的打印中,不难发现:可以通过来获取对应的枚举成员名称

ts
//定义一个描述【上下左右】方向的枚举 Direction
enum Direction {
	Up,
	Down,
	Left,
	Right
}
 
console.log(Direction);  // 打印 Direction 会看到如下内容
/* 
{
    0: 'Up',
    1: 'Down'
    2: 'Left'
    3: 'Right',
    Up: 0,
    Down: 1,
    Left: 2,
    Right: 3
}
*/
 
// 反向映射
console.log(Direction.up);
console.log(Direction[0]);
 
// 此行代码报错,枚举中的属性是只读的
Direction.Up = "shang"
js
"use strict";
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
    Direction[Direction["Left"] = 2] = "Left";
    Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));
let dir = Direction.Up;
console.log(dir);

{% endtabs %}

字符串枚举

枚举成员的值是字符串(自行定义,此时没有反向映射)。

ts
enum Direction {
    Up = "up",
    Down = "down",
    Left = "left",
    Right = "right"
}
 
let dir : Direction = Direction.Up;
console.log(dir);
js
"use strict";
var Direction;
(function (Direction) {
    Direction["Up"] = "up";
    Direction["Down"] = "down";
    Direction["Left"] = "left";
    Direction["Right"] = "right";
})(Direction || (Direction = {}));
let dir = Direction.Up;
console.log(dir);

常量枚举

官方描述:常量枚举是一种特殊枚举类型,它使用 const 关键字定义,在编译时会被内联,避免生成一些额外的代码。

使用常量枚举的 TypeScript 代码如下:

ts
const enum Directions {
    Up,
    Down,
    Left,
    Right
}
 
let x = Direction.Up;

编译后生成的 JavaScript 代码量较小:

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

8. type

type 可以为任意类型创建别名,让代码更简洁、可读性更强,同时能更方便地进行复用和扩展。

基本用法

类型别名使用 type 关键字定义,type 后跟类型名称,例如下面代码中 num 是类型别名。

ts
type num = number;
 
let price: num;
price = 100

联合类型

联合类型是一种高级类型,它表示一个值可以是几种不同类型之一。

ts
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('女');

交叉类型

交叉类型(lntersection Types)允许将多个类型合并为一个类型,合并后的类型将拥有所有被合并类型的成员。交叉类型通常用于对象类型。

ts
// 面积
type Area = {
	height: number;  // 高
    width: number;  // 宽
};
 
// 地址
type Address = {
	num: number;  // 楼号
	cell: number;  //单元号
	roon: string;  // 房间号
};
 
type House = Area & Address;
 
const house: House = {
	height: 180,
	width: 75,
	num: 6,
    cell: 3,
	room:'702'
};

9. 一个特殊情况

代码段 1(正常)

在函数定义时,限制函数返回值为 void,那么函数的返回值就必须是空。

ts
function demo(): void {
    // 返回 undefined 合法
    return undefined
    // 以下返回均不合法
    return 100
    return false
    return null
    return []
}
demo()

代码段 2(特殊)

使用类型声明限制函数返回值为 void 时,Typescript 并不会严格要求函数返回空。

ts
type LogFunc = () => void
 
const f1: LogFunc() => {
	return 180;  // 允许返回非空值
};
 
const f2: LogFunc = () => 280;  // 允许返回非空值
const f3: LogFunc = function() {
    return 380;  // 允许返回非空值
};

为什么会这样?

是为了确保如下代码成立,我们知道 Array.prototype.push 的返回一个数字,而 Array.prototype.forEach 方法期望其回调的返回类型是 void(算了就记这么写 ts 不会报错,但是最好不要这么写)。

ts
const src = [1, 2, 3];
const dst = [e];
 
src.forEach((el) => dst.push(el));

10. 复习类相关知识

Mermaid
Loading diagram…
ts
class Person {
    name: string;
    age: number;
    
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
    
    speak() {
        console.log(`我叫:${this.name},今年 ${this.age} 岁了`);
    }
}
 
class Student extends Person {
    grade: string;
    constructor(name: string, age: number, grade: string) {
        super(name, age);
        this.grade = grade;
    }
    
    speak() {
        console.log(`我叫:${this.name},今年 ${this.age} 岁了,我在 ${this.grade} 年级上学`);
    }
}
 
const person = new Person('张三', 20);
person.speak();
我叫:张三,今年 20 岁了

11. 属性修饰符

修饰符含义具体规则
public公开的可以被:类内部、子类、类外部访问
protected受保护的可以被:类内部、子类访问
private私有的可以被:类内部访问
readonly只读属性属性无法修改
ts
class Car {
	constructor(
		public vin: string,  //车辆识别码,为只读属性
        public year: number,  // 出厂年份,为只读属性
		public color: string,
        public sound: string
	){}
    
  	// 打印车辆信息
    displayInfo(){
        console.log(`识别码:${this.vin},出厂年份:${this.year},颜色:${this.color},音响:${this.sound}`);
    }
}

12. 抽象类

注意

  • 概述:抽象类是一种无法被实例化的类,专门用来定义类的结构和行为,类中可以写抽象方法,也可以写具体实现,抽象类主要用来为其派生类提供一个基础结构,要求其派生类必须实现其中的抽象方法。
  • 简记:抽象类不能实例化,其意义是可以被继承,抽象类里可以有普通方法、也可以有抽象方法

通过以下场景,理解抽象类:

我们定义一个抽象类 Package ,表示所有包裹的基本结构,任何包裏都有重量属性 weight,包裏都需要计算运费。但不同类型的包裹(如:标准速度、特快专递)都有不同的运费计算方式,因此用于计算运费的 calculate 方法是一个抽象方法,必须由具体的子类来实现。

Mermaid
Loading diagram…
ts
abstract class Package {
    // 构造方法
    constructor(public weight: number) { }
    // 抽象方法
    abstract calculate(): number;
    // 具体方法
    printPackage() {
        console.log(`Package weight: ${this.weight}kg, calculated value: ${this.calculate()}`);
    }
}
 
class StandardPackage extends Package {
    constructor(weight: number, public unitPrice: number) { super(weight); }
    calculate() { return this.weight * this.unitPrice; }
}
 
class ExpressPackage extends Package {
    constructor(weight: number, public unitPrice: number, public additional: number) { super(weight); }
    calculate() {
        if (this.weight > 10) {
            return 10 * this.unitPrice + (this.weight - 10) * this.additional;
        } else {
            return this.weight * this.unitPrice;
        }
    }
}
 
const package1 = new StandardPackage(10, 10);
console.log(package1.calculate()); // 100
const package2 = new ExpressPackage(13, 8, 2);
console.log(package2.calculate());  // 86

总结:何时使用抽象类

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

13. interface(接口)

注意

interface 是一种定义结构的方式,主要作用是为:类、对象、函数等规定一种契约,这样可以确保代码的一致性和类型安全,但要注意 interface 只能定义格式,不能包含任何实现!

ts
interface UserInterface {  // 接口命名最好体现它是一个接口
    name: string;
    readonly gender: string;  // 只读属性
    age?: number  // 可选属性
    run: (n: number) => void
}
 
const user: User = {
    name: "张三",
    gender: '男',
    age: 18
    run(n) {
        console.log(`奔跑了 ${n} 米`)
    }
}

14. 一些相似概念的区别

14.1 interface 与 type 的区别

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

八、泛型

注意

泛型允许我们在定义函数、类或接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时,才被指定具体的类型,泛型能让同一段代码适用于多种类型,同时仍然保持类型的安全性。

举例:如下代码中 <T> 就是泛型,(不一定非叫 T),设置泛型后即可在函数中使用 T 来表示该类型:

泛型函数

ts
function logData<T>(data: T): T {
    console.log(data);
    return data;
}
 
logData<number>(100);
logData<string>('hello');

泛型可以有多个

ts
function logData<T, U>(data1: T, data2: U): T | U {
	console.log(data1, data2);
    return Date.now() % 2 ? data1 : data2;
}
 
logData<number, string>(100, 'hello');
logData<string, boolean>('ok', false)

泛型接口

ts
interface PersonInterface<T> {
    name: string,
    age: number,
    extraInfo: T
}
 
let p1: personInterface<string>;
let p2: personInterface<number>;
 
p1 = { name: '张三', age: 18, extraInfo: '一个好人'};
p2 = { name: '李四', age: 18, extraInfo: 250};

泛型约束

ts
interface PersonInterface {
	name: string,
    age: number
}
 
function logPerson<T extends PersonInterface>(info: T): void {
    console.log(`我叫 ${info.name} 今年 ${info.age} 岁了`)
}
 
logPerson({ name: '张三', age: 18 });

泛型类

ts
class Person<T> {
    constructor(
        public name: string,
        public age: number,
        public extraInfo: T
    ) { }
    speak() {
        console.log(`我叫 ${this.name},今年 ${this.age} 岁了`)
        console.log(this.extraInfo)
	}
}
 
// 测试代码 1
const p1 = new Person<number>("tom", 30, 250);
 
// 测试代码 2
type JobInfo = {
    title: string;
    company: string;
}
 
const p2 = new Person<JobInfo>("tom", 30, {title: "研发总监", company : "发发发科技公司"})

九、类型声明文件

注意

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

{% tabs type %}

js
export function add(a, b) {
	return a + b;
}
 
export function mul(a, b) {
    return a * b;
}
ts
// 声明来自 demo.js 里的类型
declare function add(a: number, b: number): number;
declare function mul(a: number, b: number): number;
 
export { add, mul };
ts
import { add, mul } from "./demo.js"
 
const x = add(2, 3);
const y = mul(4, 5);
 
console.log(x, y);

{% endtabs %}