TypeStript

ts和js的区别?

TypeScript 是 JavaScript 的超集,它在 JavaScript 的基础上添加了强类型、接口、类、泛型等特性,并提供了静态类型检查等工具,能够在编写代码时更加安全、高效、可靠。与 JavaScript 相比,TypeScript 具有更强的类型系统、更严格的类型检查、更好的代码可读性和维护性。

更多的区别如下图所示:

img

TS 的数据类型

一、有哪些

typescript 的数据类型主要有如下:

  • boolean(布尔类型)
  • number(数字类型)
  • string(字符串类型)
  • array(数组类型)
  • tuple(元组类型)
  • enum(枚举类型)
  • any(任意类型)
  • null 和 undefined 类型
  • void 类型
  • never 类型
  • object 对象类型

boolean

布尔类型

代码语言:javascript

复制

let flag:boolean = true;
// flag = 123; // 错误
flag = false;  //正确

number

数字类型,和javascript一样,typescript的数值类型都是浮点数,可支持二进制、八进制、十进制和十六进制

代码语言:javascript

复制

let num:number = 123;
// num = '456'; // 错误
num = 456;  //正确

进制表示:

代码语言:javascript

复制

let decLiteral: number = 6; // 十进制
let hexLiteral: number = 0xf00d; // 十六进制
let binaryLiteral: number = 0b1010; // 二进制
let octalLiteral: number = 0o744; // 八进制

string

字符串类型,和JavaScript一样,可以使用双引号(")或单引号(')表示字符串

代码语言:javascript

复制

let str:string = 'this is ts';
str = 'test';

作为超集,当然也可以使用模版字符串``进行包裹,通过 ${} 嵌入变量

代码语言:javascript

复制

let name: string = `Gene`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }

array

数组类型,跟javascript一致,通过[]进行包裹,有两种写法:

方式一:元素类型后面接上 []

代码语言:javascript

复制

 let arr:string[] = ['12', '23'];
 arr = ['45', '56'];

方式二:使用数组泛型,Array<元素类型>

代码语言:javascript

复制

let arr:Array<number> = [1, 2];
arr = ['45', '56'];

tuple

元组类型,允许表示一个已知元素数量和类型的数组,各元素的类型不必相同

代码语言:javascript

复制

let tupleArr:[number, string, boolean];
tupleArr = [12, '34', true]; //ok
typleArr = [12, '34'] // no ok

赋值的类型、位置、个数需要和定义(声明)的类型、位置、个数一致

enum

enum类型是对 JavaScript 标准数据类型的一个补充,使用枚举类型可以为一组数值赋予友好的名字

代码语言:javascript

enum Color {Red, Green, Blue}
let c: Color = Color.Green;

any

可以指定任何类型的值,在编程阶段还不清楚类型的变量指定一个类型,不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查,这时候可以使用any类型

使用any类型允许被赋值为任意类型,甚至可以调用其属性、方法

代码语言:javascript

复制

let num:any = 123;
num = 'str';
num = true;

定义存储各种类型数据的数组时,示例代码如下:

代码语言:javascript

复制

let arrayList: any[] = [1, false, 'fine'];
arrayList[1] = 100;

null 和 undefined

JavaScriptnull表示 "什么都没有",是一个只有一个值的特殊类型,表示一个空对象引用,而undefined表示一个没有设置值的变量

默认情况下nullundefined是所有类型的子类型, 就是说你可以把 nullundefined赋值给 number类型的变量

代码语言:javascript

复制

let num:number | undefined; // 数值类型 或者 undefined
console.log(num); // 正确
num = 123;
console.log(num); // 正确

但是ts配置了--strictNullChecks标记,nullundefined只能赋值给void和它们各自

void

用于标识方法返回值的类型,表示该方法没有返回值。

代码语言:javascript

复制

function hello(): void {
    alert("Hello Runoob");
}

never

never是其他类型 (包括nullundefined)的子类型,可以赋值给任何类型,代表从不会出现的值

但是没有类型是 never 的子类型,这意味着声明 never 的变量只能被 never 类型所赋值。

never 类型一般用来指定那些总是会抛出异常、无限循环

代码语言:javascript

复制

let a:never;
a = 123; // 错误的写法

a = (() => { // 正确的写法
  throw new Error('错误');
})()

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

// 返回值为 never 的函数可以是无法被执行到的终止点的情况
function loop(): never {
    while (true) {}
}

object

对象类型,非原始类型,常见的形式通过{}进行包裹

代码语言:javascript

复制

let obj:object;
obj = {name: 'Wang', age: 25};

二、总结

javascript基本一致,也分成:

  • 基本类型
  • 引用类型

在基础类型上,typescript增添了voidanyenum等原始类型

参考文献

  • https://www.tslang.cn/docs/handbook/basic-types.html

any 与 unknown 类型的区别

1、any 类型表示任意类型,放弃了 ts 类型检查,ts 中应该少用

2、unkonw 类型是暂时未知类型(之后会知道),仍然会进行 ts 的类型检查

(1)any 代表任意类型, 是不做任何检查,相当于不使用 ts,不建议使用,使用 a as any as string 之类的,可以让类型检查通过,但是不建议使用。

(2)unknown 代表未知类型,更加严格,在对 unknown 类型的值执行大多数操作之前,必须进行某种形式的检查。 unknown 因为未知性质,不支持属性访问,不允许赋值给其他明确类型的变量,但是任何值都可以赋值给 unknown

unknown 还是可以保证类型安全的,在需要使用顶级类型的时候,还是用 unknown,使用类型断言 a as number 或者 a as any as string 之类的,也可以让类型检查通过,但是有的时候,断言错了,编译器不会报错,运行的时候还是会报错的,不过 any 和 unknown 还是建议使用 unknown。

隐式类型推断:

对于 any,它会关闭 TypeScript 的类型检查,允许你在任何地方使用任何属性或方法,而不会得到编译器的提示。这样可能导致运行时错误。 对于 unknown,你不能直接对其进行任何操作,因为 TypeScript 不知道它的具体类型。你必须在使用之前进行类型检查或类型断言。

类型赋值: any 允许你将其赋值给任何类型,而不会引发错误。 unknown 不能直接赋值给其他类型,除非进行类型检查或类型断言。

TS 的优势有哪些

TS 是 JS 的超集,它添加了静态类型检查和其他编程功能,提供了更强大的开发工具和语言特性。

优点

  • 静态类型检查

TypeScript 引入了类型系统,允许在开发过程中捕获和修复许多常见的错误。

它可以在编译时检测类型错误,提供更早的错误发现和自动补全,减少了运行时错误和调试的时间。

  • 更好的代码维护性

静态类型检查和强大的开发工具使得代码更具可读性、可维护性和可扩展性。通过类型注解和接口定义,可以更清晰地理解代码的意图,降低了代码的理解和维护成本。

  • 提高开发效率

TypeScript 提供了 ECMAScript 标准之上的新语言特性,如类、模块、箭头函数等,以及更强大的类型系统。这些功能和工具提供了更好的开发体验,加快了开发速度,减少了冗余的代码。

  • 适用于大型项目

TypeScript 的类型系统和模块化支持使得它特别适用于大型项目的开发。它提供了更好的组织和管理代码的能力,使得团队协作更加高效,并降低了重构和维护的风险。

  • 强大的生态系统

TypeScript 是一个受欢迎的开发语言,具有广泛的社区支持和活跃的生态系统。许多流行的框架和库都提供了 TypeScript 的类型定义,使得与这些工具的集成更加便捷。

缺点

学习曲线:TypeScript 比纯粹的 JavaScript 有更多的特性和概念,因此学习 TypeScript 需要一定的时间和学习成本。开发人员需要了解类型系统、类型注解的语法以及其他 TypeScript 特有的概念。

增加编译时间:由于 TypeScript 需要进行类型检查和编译过程,在一些情况下会增加编译时间。尤其是对于大型项目或使用复杂类型系统的项目,编译时间可能会显著增加。

为什么要使用 TypeScript

  • 更好的代码质量和可维护性

TypeScript 的类型检查可以帮助捕获潜在的错误,提高代码质量,并减少运行时错误。它提供了更好的可读性和可维护性,使得代码更易于理解和修改。

  • 提高开发效率

TypeScript 提供了更强大的开发工具和语言特性,如自动补全、代码导航和重构支持。这些功能提高了开发效率,加快了开发速度。

  • 更好的团队协作

TypeScript 的类型系统提供了清晰的接口定义和代码约定,促进了团队协作。开发人员可以更容易地理解和使用彼此的代码,减少了沟通和集成的成本。

广泛的生态系统支持:TypeScript 在 JavaScript 社区具有广泛的支持和活跃的生态系统。许多流行的框架和库都提供了 TypeScript 的类型定义,使得与这些工具的集成更加顺畅。

综上所述,TypeScript 的优点包括静态类型检查、更好的代码质量与维护性、提高开发效率和适用于大型项目,而缺点包括学习曲线和编译时间增加。

选择使用 TypeScript 取决于项目的规模和需求,以及开发团队的技术栈和偏好。对于大型项目、需要更好的代码质量和可维护性、以及倾向于使用静态类型检查的团队来说,TypeScript 是一个强大的选择。

联合类型&交叉类型

1. 定义

1.1. 联合类型(|)

在 TS 中,联合类型表示:一个值可以是多种类型之一,使用逻辑“或”( | )运算符来分隔多个类型。

一个联合类型的变量,在使用时可以是多个类型中的任意一种。

type UnionTypes = Type1 | Type2 | Type3;

1.2. 交叉类型(&)

在 TS 中,交叉类型表示:同时具备多种类型的值,使用逻辑“与”( & )运算符进行组合。

一个交叉类型的变量,将同时拥有多个类型的属性和方法。

type IntersectionTypes = Type1 & Type2 & Type3;

2. 联合类型

2.1. 基础联合类型

当一个变量可以是多种不同的类型时,可以使用联合类型来定义它。

例如,一个变量可以是 string 类型或者 number 类型。

let data: string | number;
data = 'hello ts';
data = 123;
data = false; // 编译错误:不能将类型“boolean”分配给类型“string | number”。ts(2322)

上面这段代码中,我们定义了一个变量 data,类型为 number 和 string 的联合类型,因此,data 的值只能是这两种类型中的其中一种,复制其它类型的值会报错。

2.2. 对象联合类型

对象联合类型只能访问联合中所有类型共有的成员。

interface Admin {
  name: string;
  age: number;
}
interface User {
  name: string;
  sayHi(): void;
}
declare function Employee(): Admin | User;
let employee = Employee();
employee.name = 'Echo';
// 下面语句会报错:age属性不是 Admin 和 User 共有的属性
employee.age = 26; // 编译错误:类型“Admin | User”上不存在属性“age”。类型“User”上不存在属性“age”。ts(2339)

上面这段代码中,定义了两个接口 Admin 和 User,接着使用 declare function 声明了一个 Employee 函数,该函数的返回类型为 Admin 或 User。之后通过调用 Employee 函数并将返回值赋给了 employee 变量。接着将 employee 对象中 name 属性的值设置为 'Echo' 是可以的,因为 name 属性是 Admin 和 User 共有的属性。而将 employee 对象中 age 属性的值设置为 26 时会出现编译错误,错误信息指出类型 Admin | User 上不存在属性 age。这是因为 age 属性只存在于 Admin 接口中,而不属于 User 接口。

造成该错误的原因是,TypeScript 在联合类型上只能访问联合类型中所有类型的共有属性和方法。 因此,通过联合类型的变量只能访问 name 属性,而不能访问 age 属性。

2.3. 字面量联合类型

联合类型可以与字面量类型一起使用,用于限定一个值只能是某几个特定的值之一。

let direction: "Up" | "Right" | "Down" | "Left";
direction = "Right";
direction = "none"; // 编译错误,只能取值为 "Up" | "Right" | "Down" | "Left"

3. 交叉类型

在 TypeScript 中,交叉类型(Intersection Types)允许我们将多个类型合并为一个新的类型。

使用交叉类型可以将多个对象的属性和方法合并到一个新的对象中。

type Person = {
  name: string;
}
type User = {
  age: number;
}
let person: Person & User;
person = {
  name: 'Echo',
  age: 26,
}
// 编译错误:
// 不能将类型“{ name: string; }”分配给类型“Person & User”。
// 类型 "{ name: string; }" 中缺少属性 "age",但类型 "User" 中需要该属性。ts(2322)
// index.ts(7, 3): 在此处声明了 "age"。
person = {
  name: 'Steven',
}

上面这段代码中,我们定义了 Person 和 User 两个类型,然后,我们定义一个变量 person,它的类型是使用交叉类型 Person & User 来创建的一个新类型,那么,此时变量 person 就同时具备了 name 和 age 属性。

3.1. 交叉类型的成员类型是基础类型

交叉类型的成员类型可以为任意类型,但需要注意的是,如果交叉类型的成员类型是基础类型时,交叉类型的结果是 never。

type T1 = string & number;   // 等同于 type T1 = never
type T2 = number & boolean;  // 等同于 type T2 = never

3.2. 交叉类型的成员类型是对象类型

当交叉类型的成员类型为对象类型时,结果类型又会是什么?

下面我们看一个简单的例子:

type TypeA = {
  x: number;
  y: number;
}
type TypeB = {
  y: number;
  z: number;
}
type TypeC = {
  z: number;
}

上面这段代码中,我们定义了三个类型:TypeA、TypeB 和 TypeC,分别表示类型 A、B 和 C,类型 A 具有属性成员 x 和 y,类型 B 具有属性成员 y 和 z,类型 C 具有属性成员 z,每个类型具有不同的属性成员。

type MergedType = TypeA & TypeB & TypeC;

上面这段代码中,我们使用交叉类型 TypeA & TypeB & TypeC 创建了一个新的类型 MergedType,它包含了类型 A、B 和 C 的属性成员,那么,合并后的交叉类型的成员类型为:属性成员 x 的类型是 A 的类型,属性成员 y 的类型是 A 和 B 的交叉类型,属性成员 z 的类型是 B 和 C 的交叉类型。

let t: MergedType;
const t1 = {
  x: 1,
  y: 2,
  z: 3,
}
const t2 = {
  x: 10,
  y: 20,
}
t = t1;
// 编译错误:
// 不能将类型“{ x: number; y: number; }”分配给类型“MergedType”。
// 类型 "{ x: number; y: number; }" 中缺少属性 "z",但类型 "TypeB" 中需要该属性。ts(2322)
// index.ts(10, 3): 在此处声明了 "z"。
t = t2;

上面这段代码中,定义了一个变量 t,它的类型是 TypeA & TypeB & TypeC 组成的交叉类型,然后再定义了两个变量 t1 和 t2,t1 同时满足 TypeA、TypeB 和 TypeC 类型约束,因此能赋值给交叉类型 t。而 t2 满足 TypeA 类型约束,是 TypeA 类型,但并不能赋值给交叉类型 t,当 t2 赋值给 t 的时候,编译器会报错。

由此可见:交叉类型的类型成员由各个类型成员的属性成员的并集组成,并且这些属性成员的类型是各个成员类型的交叉类型。这种规则使得交叉类型能够将多个类型的属性成员合并到一个类型中,并且可以同时访问这些属性成员。

3.3. 成员类型合并

如果交叉类型的成员类型中有相同的类型,合并后的交叉类型将只保留一份该成员的类型。

type T1 = string & string;   // 等同于 type T1 = string
type T2 = string & string & string;  // 等同于 type T2 = string

上面这段代码中,类型 T1 由两个 string 构成,由于成员类型相同,所以合并成为一个 string。类型 T2 由三个 string 构成,由于成员类型相同,所以合并成为一个 string。

3.4. 交叉类型的索引签名

当交叉类型的成员类型之一具有数字索引签名(即可通过数字索引访问)或字符串索引签名(即可通过字符串索引访问)时,结果类型也将包含相应的数字索引签名或字符串索引签名。

结果类型的索引签名值类型是各个成员类型索引签名值类型的交叉类型。也就是说,通过交叉类型合并的结果类型的索引签名值类型将是各个成员类型索引签名值类型的交叉类型。

type TypeA = {
  [key: string]: string;
};
type TypeB = {
  [key: number]: string;
};
type MergedType = TypeA & TypeB;
const mergedObject: MergedType = {
  name: 'Echo',
  gender: 'Male',
  city: 'Guang Zhou',
  1: 'abcd',
};
console.log(mergedObject['name']);   // 输出:Echo
console.log(mergedObject['gender']); // 输出:Male
console.log(mergedObject['city']);   // 输出:Guang Zhou
console.log(mergedObject[1]);        // 输出:abcd

上面这段代码中,定义了两个类型 TypeA 和 TypeB,其中,TypeA 具有字符串索引签名,TypeB 具有数字索引签名,也就是说,TypeA 允许使用字符串作为索引,而 TypeB 允许使用数字作为索引。然后,使用交叉类型 TypeA & TypeB 创建了一个新的类型 MergedType,它包含了 TypeA 和 TypeB 的索引签名。接着,我们创建了一个名为 mergedObject 的对象,它的类型指定为交叉类型 MergedType,该对象可以通过数字索引或字符串索引来访问,并给这些索引赋予了相应的值。最后,我们通过索引访问 mergedObject 对象的值来验证交叉类型的索引签名的合并情况。

3.5. 交叉类型的调用签名

当交叉类型的成员类型中至少有一个具有调用签名时,交叉类型的结果类型也会包含这个调用签名。

换句话说,交叉类型中至少一个成员的调用签名会被合并到结果类型中。

此外,如果交叉类型的多个成员类型都有调用签名,那么结果类型将会形成调用签名重载的结构。调用签名重载允许我们为同一个函数提供多个不同的调用方式,具体取决于参数类型和返回值类型。

可以将交叉类型的成员类型的调用签名视为函数的签名,交叉类型的结果类型即为这些签名的合并。

type FunctionA = (x: number, y: number) => number;
type FunctionB = (x: string, y: string) => string;
type FunctionType = FunctionA & FunctionB;
const option: FunctionType = (x, y) => x + y;
console.log(option(10, 20));     // 输出: 30
console.log(option('a', 'b'));   // 输出: ab

上面这段代码中,定义了两个类型 FunctionA 和 FunctionB,它们接收 x 和 y 两个参数,其中,FunctionA 两个参数的类型和函数返回值的类型都是 number 类型,FunctionB 两个参数的类型和函数返回值的类型都是 string 类型。然后,使用交叉类型 FunctionA & FunctionB 创建了一个新的类型 FunctionType,这个交叉类型包含了两个成员类型的调用签名。最后,我们创建了一个名为 option 的变量,它的类型被定义为 FunctionType,也就是 FunctionA 和 FunctionB 的交叉类型。我们可以使用 option 等同于调用两个函数的方式来执行相应的运算,option(10, 20) 相当于加法运算,输出结果为:30,option('a', 'b') 相当于字符串的拼接,输出结果为:ab。

3.6. 交叉类型的构造签名

当交叉类型的成员类型中至少有一个具有构造签名时,交叉类型的结果类型也会包含这个构造签名。

换句话说,交叉类型中至少存在一个成员的构造签名会被合并到结果类型中。

如果交叉类型的多个成员类型都具有构造签名,那么结果类型将形成构造签名重载的结构。构造签名重载允许我们为同一个类提供多个不同的构造方式,具体取决于参数列表。

interface Foo {
  new (name: string): string
}
interface Bar {
  new (name: number): number;
}
type FooBar = Foo & Bar;
declare const T: FooBar;
const instance1 = new T('Echo');
const instance2 = new T(26);

上面这段代码中,我们定义了两个接口 Foo 和 Bar,它们都具有构造签名,分别接受不同的参数类型并返回对应的类型。接着,我们使用交叉类型 Foo & Bar 创建了一个新的类型 FooBar,它是 Foo 和 Bar 的交叉类型,意味着 FooBar 同时具备了 Foo 和 Bar 接口的构造签名。然后,通过 declare 关键字声明了一个常量 T,它的类型被定义为 FooBar。接着我们创建了两个实例 instance1 和 instance2。由于 T 的类型为 FooBar,我们可以使用 new T 的语法来实例化对象。对于 instance1,使用字符串 'Echo' 作为参数传递给构造函数,这符合 Foo 接口中定义的构造签名,所以实例化成功,返回一个字符串类型的实例。对于 instance2,使用数字 26 作为参数传递给构造函数,这符合 Bar 接口中定义的构造签名,所以实例化成功,返回一个数字类型的实例。

4. 总结

  • 联合类型只能访问共有的属性和方法。例如,如果一个变量是 number 类型或者 string 类型,那么只能使用这两种类型共有的属性和方法。
  • 联合类型中的变量,如果在特定条件下可以判断出其具体的类型,可以使用类型断言(as 语法)来告诉编译器具体的类型。
  • 交叉类型的结果包含了所有成员类型的属性和方法,通过合并同名成员实现。属性会合并为并集类型,方法会合并为联合类型。
  • 当成员类型具有调用签名或构造签名时,交叉类型的结果将形成相应签名的重载。

类型断言是什么,类型断言的场景

类型断言(Type Assertion)是 TypeScript 中的一种机制,允许开发者在编译时显式地指定一个值的类型。类型断言不会改变值的实际类型,只是告诉 TypeScript 编译器按某种类型去处理这个值。类型断言常用于需要在不同类型之间进行转换的情况下,尤其是在处理动态类型的数据时。

类型断言的两种形式

  1. 尖括号形式<Type>value):

    let someValue: any = "hello";
    let strLength: number = (<string>someValue).length;
    
  2. as 关键字形式value as Type):

    let someValue: any = "hello";
    let strLength: number = (someValue as string).length;
    

这两种形式在功能上是等价的,但后者 as 形式是推荐使用的,因为它更清晰易读。

类型断言的使用场景

类型断言通常用于以下几种场景:

1. 从 any 类型转换为具体类型

当你有一个 any 类型的变量,但你知道它的实际类型时,可以使用类型断言来指定它的类型。

let anyValue: any = "hello";
let strLength: number = (anyValue as string).length; // 或者 <string>anyValue

2. 从联合类型(Union Types)中选择一种类型

当一个变量是多种类型的联合类型时,可以通过类型断言来选择其中的一种类型。

function getLength(something: string | number): number {
  return (something as string).length; // 错误:number 没有 length 属性
}

// 正确的做法应该是使用类型保护
function isString(value: any): value is string {
  return typeof value === 'string';
}

function getLength(something: string | number): number {
  if (isString(something)) {
    return something.length;
  }
  return something.toString().length;
}

3. 处理 DOM 元素

在处理 DOM 元素时,经常需要将 HTMLElementEventTarget 等类型断言为更具体的类型。

const div = document.getElementById('myDiv') as HTMLDivElement;
const text = div.textContent; // div 已经被断言为 HTMLDivElement

4. 泛型函数中的类型断言

在使用泛型函数时,可能需要断言返回值的类型。

function getData<T>(id: number): T {
  // 返回数据
  return {} as T;
}

const myData = getData<{ id: number; name: string }>(1);
console.log(myData.id); // TypeScript 知道 myData 是 { id: number; name: string }

5. 处理回调函数的返回值

在处理回调函数时,如果回调函数的返回值类型不确定,可以通过类型断言来指定类型。

function fetchData(callback: () => any) {
  return callback();
}

const data = fetchData(() => ({ id: 1, name: 'John' })) as { id: number; name: string };
console.log(data.name); // TypeScript 知道 data 是 { id: number; name: string }

注意事项

  1. 谨慎使用:类型断言绕过了 TypeScript 的类型检查,因此应该谨慎使用,确保你知道自己在做什么。
  2. 类型保护:尽可能使用类型保护(如 typeofinstanceof 或自定义类型保护函数)而不是类型断言,以增强类型安全性。
  3. 重构和优化:如果频繁使用类型断言,可能表明类型系统或代码设计存在问题,应考虑重构代码或优化类型定义。

通过合理使用类型断言,可以在 TypeScript 中更灵活地处理不同类型的数据,同时保持代码的类型安全性。Typescript 中泛型是什么?

范型

一、范型是什么

我们可以把泛型比喻为一个类型占位符open in new window,它告诉编译器:这里有一个类型参数,我现在不确定具体是什么类型,但稍后会告诉你。

通过使用泛型,我们可以编写更灵活、更可复用的代码。它允许我们在定义函数、类或接口时使用类型占位符来表示类型,而不直接指定具体的类型。这样,在实际使用时,我们可以传入不同的类型参数,使得代码可以适用于多种情况。

泛型程序设计(generic programming)是程序设计语言的一种风格或范式

泛型允许我们在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型 在typescript中,定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性

假设我们用一个函数,它可接受一个 number 参数并返回一个 number 参数,如下写法:

function returnItem (para: number): number {
	return para
}

如果我们打算接受一个 string 类型,然后再返回 string类型,则如下写法:

function returnItem (para: string): string {
    return para
}

上述两种编写方式,存在一个最明显的问题在于,代码重复度比较高

虽然可以使用 any类型去替代,但这也并不是很好的方案,因为我们的目的是接收什么类型的参数返回什么类型的参数,即在运行时传入参数我们才能确定类型

这种情况就可以使用泛型,如下所示:

function returnItem<T>(para: T): T {
    return para
}

可以看到,泛型给予开发者创造灵活、可重用代码的能力

二、使用方式

泛型通过<>的形式进行表述,可以声明:

  • 函数
  • 接口

函数声明

声明函数的形式如下:

function returnItem<T>(para: T): T {
    return para
}

定义泛型的时候,可以一次定义多个类型参数,比如我们可以同时定义泛型 T 和 泛型 U

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}

swap([7, 'seven']); // ['seven', 7]

接口声明

声明接口的形式如下:

interface ReturnItemFn<T> {
    (para: T): T
}

那么当我们想传入一个 number 作为参数的时候,就可以这样声明函数:

const returnItem: ReturnItemFn<number> = para => para

类声明

使用泛型声明类的时候,既可以作用于类本身,也可以作用与类的成员函数

下面简单实现一个元素同类型的栈结构,如下所示:

class Stack<T> {
    private arr: T[] = []
    public push(item: T) {
        this.arr.push(item)
    }
    public pop() {
        this.arr.pop()
    }
}

使用方式如下:

const stack = new Stacn<number>()

如果上述只能传递 stringnumber 类型,这时候就可以使用 <T extends xx> 的方式猜实现约束泛型,如下所示:

预览

除了上述的形式,泛型更高级的使用如下:

例如要设计一个函数,这个函数接受两个参数,一个参数为对象,另一个参数为对象上的属性,我们通过这两个参数返回这个属性的值

这时候就设计到泛型的索引类型和约束类型共同实现

索引类型、约束类型

索引类型 keyof T 把传入的对象的属性类型取出生成一个联合类型,这里的泛型 U 被约束在这个联合类型中,如下所示:

function getValue<T extends object, U extends keyof T>(obj: T, key: U) {
  return obj[key] // ok
}

上述为什么需要使用泛型约束,而不是直接定义第一个参数为 object类型,是因为默认情况 object 指的是{},而我们接收的对象是各种各样的,一个泛型来表示传入的对象类型,比如 T extends object

使用如下图所示:

预览

多类型约束

例如如下需要实现两个接口的类型约束:

interface FirstInterface {
  doSomething(): number
}

interface SecondInterface {
  doSomethingElse(): string
}

可以创建一个接口继承上述两个接口,如下:

interface ChildInterface extends FirstInterface, SecondInterface {

}

正确使用如下:

class Demo<T extends ChildInterface> {
  private genericProperty: T
3
4  constructor(genericProperty: T) {
5    this.genericProperty = genericProperty
6  }
7  useT() {
8    this.genericProperty.doSomething()
9    this.genericProperty.doSomethingElse()
10  }
11}

通过泛型约束就可以达到多类型约束的目的

三、应用场景

通过上面初步的了解,后述在编写 typescript 的时候,定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性的时候,这种情况下就可以使用泛型

灵活的使用泛型定义类型,是掌握typescript 必经之路

TS interface 和 type 的区别

  1. 定义范围和用途

    interface主要用于定义对象类型,它描述了一个对象的形状,可以包含属性、方法、事件等。接口还可以通过extends关键字来继承其他接口或类,实现代码的复用和扩展。

    此外,接口支持声明合并,允许在同一个接口中添加新的成员。

    type(类型别名)则更为灵活,它可以用来定义任何类型,包括基本类型(如numberstring等)、联合类型(如number | string)、交叉类型(通过&操作符合并多个类型)、元组等。类型别名不支持继承,但可以通过交叉类型实现类似的功能。

  2. 扩展性

    interface可以通过extends关键字来扩展多个接口或类,实现代码的模块化和可维护性。这种扩展性使得接口非常适合用于描述复杂的对象结构。type没有直接的扩展功能,但可以通过交叉类型(使用&操作符)来合并多个类型,实现类似继承的效果。这种灵活性使得类型别名在处理复杂类型时更为强大。

  3. 声明方式

    interface声明时,如果遇到重复的成员声明,它们会被合并到同一个接口中。这种合并机制允许在接口中逐步添加或修改成员,而不需要重新声明整个接口。type声明时,如果遇到重复的声明(如type Person = { name: string }重复声明),会导致编译错误。这是因为类型别名一旦声明就无法更改它们,它们是引用而不是定义。

  4. 使用场景

    当需要描述对象的结构、方法或事件时,应使用interface。例如,定义一个用户接口时,可以包含用户名、密码等属性以及登录方法。

    当需要定义基本类型别名、联合类型、交叉类型或元组时,应使用type。例如,为字符串类型定义别名或创建联合类型来表示可能的数据类型。

综上所述,interfacetype在 TypeScript 中各有其用途和限制,选择使用哪种方式取决于具体的需求和场景。

你是怎么理解范型的?

我们可以把泛型比喻为一个类型占位符,它告诉编译器:“这里有一个类型参数,我现在不确定具体是什么类型,但稍后会告诉你。”

通过使用泛型,我们可以编写更灵活、更可复用的代码。它允许我们在定义函数、类或接口时使用类型占位符来表示类型,而不直接指定具体的类型。这样,在实际使用时,我们可以传入不同的类型参数,使得代码可以适用于多种情况。

参考:https://zhuanlan.zhihu.com/p/661807341

TS 中 .d.ts 如何理解

.d.ts 文件是 TypeScript 的类型声明文件,它们的主要作用是为 JavaScript 库提供类型支持,使我们能够在 TypeScript 中使用这些库时获得类型检查和智能提示。.d.ts 文件描述了库或模块的结构、函数、类、接口以及其他类型信息,让 TypeScript 编译器了解这些库的类型约束。

any 和 never 的区别

在TypeScript中,anynever 是两种非常不同的类型,它们各自有着特定的用途和含义。下面是这两种类型的详细区别:

any

  • 定义any 类型表示一个值可以是任何类型。它关闭了 TypeScript 的类型检查功能,允许你对变量进行任何操作。

  • 使用场景

    • 当你需要逐步将现有的 JavaScript 代码迁移到 TypeScript 时,可以暂时使用 any 来避免大量的类型错误。
    • 在处理动态内容或不确定类型的外部数据时,如从用户输入或第三方库获取的数据。
    • 作为临时解决方案,在开发过程中当你还不确定某个变量的具体类型时。
  • 特点

    • any 类型可以赋值给任何其他类型,也可以接受任何类型的值。
    • 使用 any 可以绕过编译时的类型检查,但这也意味着失去了类型安全的优势。
  • 示例

    let value: any = 42;
    value = 'hello'; // 合法
    value.toUpperCase(); // 合法,但可能在运行时出错
    

never

  • 定义never 类型表示那些永远不会发生的情况。它通常用于函数返回类型,表示该函数不会正常返回(例如抛出异常或进入无限循环)。

  • 使用场景

    • 函数总是抛出异常。
    • 函数进入了一个无限循环,永远不会结束。
    • 表示某些不可能达到的状态或条件。
  • 特点

    • never 类型是所有类型的子类型,没有任何类型是 never 的子类型。
    • 没有值可以被直接赋值为 never 类型。
    • never 类型可以赋值给任何其他类型,因为它是所有类型的子类型。
  • 示例

    // 抛出异常的例子
    function throwError(message: string): never {
        throw new Error(message);
    }
    
    // 无限循环的例子
    function loopForever(): never {
        while (true) {}
    }
    

主要区别

  1. 类型安全性

    • any 关闭了类型检查,使得你可以对变量执行任何操作,但这也会导致潜在的运行时错误。
    • never 则更加严格,表示函数永远不会有正常的返回路径,有助于捕获逻辑错误和提高代码的可读性。
  2. 使用目的

    • any 通常用于处理不确定类型的外部数据或逐步迁移现有JavaScript代码。
    • never 用于表示函数永远不会正常完成,或者表示某些不可能达到的状态。
  3. 类型系统中的角色

    • any 是一种顶级类型(top type),它可以接受任何类型的值,并且可以赋值给任何类型的变量。
    • never 是一种底类型(bottom type),没有值可以直接赋值给 never,但它可以赋值给任何其他类型。

总结来说,any 提供了最大的灵活性,但也牺牲了类型安全;而 never 则是一种特殊的类型,用于表示不可能的情况,帮助开发者更好地理解和维护代码。在实际开发中,应尽量减少 any 的使用,合理利用 never 来增强代码的表达力和安全性。

never 的应用场景

never 类型在 TypeScript 中是一种非常特殊的类型,表示那些永远不会发生的值的类型。它主要用于以下几种场景:

1. 抛出异常的函数

如果一个函数总是抛出异常,并且没有正常的返回路径,那么它的返回类型可以标记为 never

function throwError(message: string): never {
    throw new Error(message);
}

在这个例子中,throwError 函数总是抛出一个错误,因此它永远不会正常返回。使用 never 类型明确地表示了这一点。

2. 无限循环

如果一个函数进入了一个无限循环,并且不会正常结束,那么它的返回类型也可以标记为 never

function loopForever(): never {
    while (true) {
        // 无限循环
    }
}

这个函数会一直运行下去,永远不会返回,因此其返回类型是 never

3. 条件分支中的不可能情况

在某些情况下,你可能希望确保某个条件分支是不可能达到的。这时可以使用 never 类型来表示这种情况。

function processValue(value: string | number) {
    if (typeof value === 'string') {
        console.log('String:', value.toUpperCase());
    } else if (typeof value === 'number') {
        console.log('Number:', value * 2);
    } else {
        const exhaustiveCheck: never = value; // 确保所有情况都被处理
        throw new Error(`Unexpected type: ${exhaustiveCheck}`);
    }
}

在这个例子中,value 只能是 stringnumber,所以 else 分支应该是不可能达到的。通过将 value 赋值给 never 类型的变量,TypeScript 会在编译时检查是否有遗漏的情况。如果将来添加了新的联合类型成员,但没有更新 processValue 函数的逻辑,编译器会报错。

4. 排除类型的交集

never 类型还可以用于表示两个不相交类型的交集。例如,如果你有两个互斥的类型,它们的交集就是 never

type A = { kind: 'A' };
type B = { kind: 'B' };

type ABIntersection = A & B; // 这个类型实际上是 never

在这个例子中,AB 是互斥的类型,它们的交集是空的,因此类型 ABIntersection 实际上是 never

5. 类型保护

在类型保护中,never 也可以用来确保所有情况都被处理。

function isStringOrNumber(value: any): value is string | number {
    return typeof value === 'string' || typeof value === 'number';
}

function printValue(value: any) {
    if (isStringOrNumber(value)) {
        console.log(value);
    } else {
        const exhaustiveCheck: never = value;
        throw new Error(`Unexpected type: ${exhaustiveCheck}`);
    }
}

在这个例子中,printValue 函数使用了类型保护 isStringOrNumber,并在 else 分支中使用 never 类型来确保所有情况都被处理。

总结

never 类型在 TypeScript 中主要用于表示那些永远不会发生的情况。它可以帮助开发者更好地表达代码的意图,提高代码的可读性和安全性。通过合理使用 never 类型,你可以更清晰地定义函数的行为和处理不可能的情况。

void 和 never 区别

在TypeScript中,voidnever 都是类型,但它们表示的意义和使用场景有所不同。

void

  • 定义void 类型用于表示没有任何返回值的函数。当一个函数不返回任何值时,它的返回类型就是 void
  • 使用场景:通常用在那些执行某些操作但不产生结果(即不返回数据)的函数上。
  • 示例
    function logMessage(message: string): void {
        console.log(message);
    }
    

never

  • 定义never 类型表示那些永远不会正常完成或永远不会有返回值的情况。它通常用来描述总是抛出异常的函数或者根本就不会终止执行的函数(如无限循环)。
  • 使用场景
    • 函数总是抛出错误。
    • 函数进入了一个无限循环,不会到达终点。
  • 示例
    // 抛出错误的例子
    function throwError(message: string): never {
        throw new Error(message);
    }
    
    // 无限循环的例子
    function loopForever(): never {
        while (true) {}
    }
    

主要区别

  1. 返回值

    • void 表示函数没有返回值,但它可以正常结束。
    • never 表示函数不会正常结束,即它要么抛出异常,要么进入一个无法退出的状态。
  2. 用途

    • void 适用于常规的无返回值函数,例如简单的日志记录、副作用处理等。
    • never 则更适用于特定的编程模式,比如明确指出某个函数不应该有正常的返回路径,这有助于静态分析工具更好地理解代码的行为。
  3. 类型系统中的角色

    • void 是一种相对宽松的类型,可以被赋值给 any 或者 unknown 等更广泛的类型。
    • never 是 TypeScript 中的一种底类型(bottom type),意味着它可以被赋值给任何其他类型,但没有任何类型可以直接赋值给 never

总结来说,voidnever 虽然都表示某种形式的“无”,但是 never 更加严格且具有特殊的语义含义,主要用于表示程序控制流的特殊情况。

any 的使用场景

在TypeScript中,any 类型是一种非常灵活但也相对危险的类型,因为它允许你绕过类型检查。尽管如此,在某些情况下使用 any 是合理且必要的。以下是一些常见的 any 使用场景:

  1. 逐步迁移现有JavaScript代码

    • 当你从纯JavaScript项目迁移到TypeScript时,可能不会立即为所有变量和函数添加类型注解。这时可以先将一些不确定类型的变量标记为 any,以便项目能够顺利编译。随着项目的逐步重构,你可以逐渐移除 any 并替换为具体的类型。
  2. 与第三方库交互

    • 如果你在使用的第三方库没有提供类型定义文件(.d.ts),或者这些类型定义不够准确或不完整,你可能需要使用 any 来处理这些库返回的对象或函数。不过,推荐的做法是创建自己的类型定义文件或寻找社区提供的类型定义。
  3. 动态内容

    • 当你的应用需要处理来自用户输入或其他外部来源的数据,并且这些数据结构事先无法确定时,可以使用 any。例如,JSON解析后的数据可能是任何类型,这时可以先用 any 接收,再进行进一步的类型检查或转换。
  4. 临时解决方案

    • 在开发过程中,有时会遇到暂时不清楚具体类型的情况,此时可以先使用 any 作为占位符。但要注意这应该是一个临时措施,最终应替换为明确的类型。
  5. 性能考虑

    • 在极少数情况下,为了提高编译速度或减少类型检查带来的开销,可能会选择在不影响功能的地方使用 any。但这通常不是首选做法,因为牺牲了类型安全。
  6. 库或框架内部实现

    • 在编写库或框架时,有时候为了保持灵活性,允许传递任意类型的参数,可能会使用 any。但这种情况应当谨慎处理,确保不会影响到库的使用者。

注意事项

  • 尽量避免滥用:虽然 any 提供了很大的灵活性,但它也破坏了TypeScript提供的类型安全性。因此,应尽量避免不必要的使用。
  • 逐步改进:如果必须使用 any,建议将其视为一个临时状态,并计划在未来引入更严格的类型控制。
  • 文档说明:当确实需要使用 any 时,最好在代码中加上注释,解释为什么这里使用 any 以及未来是否有改进计划。

总之,any 应该被看作一种最后的手段,仅在其他方法都不可行的情况下才使用。通过合理的类型设计和使用泛型等高级特性,大多数情况下都可以避免对 any 的依赖。

ts中omit用来做什么的

Omit 是 TypeScript 提供的一个高级类型工具,用于从一个类型中排除指定的键,从而构造一个新的类型。它在处理对象类型时非常有用,可以帮助你轻松创建不包含某些特定属性的类型。

它的主要使用场景有以下几个方面:

  1. 去除不需要的属性:当你有一个类型,但不需要其中的一些属性时,可以使用 Omit 去除这些属性。
  2. 创建子类型:从一个大型类型中派生出较小的子类型,只包含某些特定的属性。

链接:https://juejin.cn/post/7384266820466245684

ts有那些内置范型

TypeScript(通常简称为TS)是一门静态类型的JavaScript超集,它添加了类型系统和其他特性到JavaScript中。在TypeScript中,泛型是一种强大的功能,允许你创建可以处理多种数据类型的函数、接口和类。TypeScript内置了一些常用的泛型类型,这些泛型可以帮助开发者更方便地进行类型安全的编程。以下是一些常见的内置泛型:

  1. Array<T> - 用于表示一个元素类型为T的数组。

    let numbers: Array<number> = [1, 2, 3];
    
  2. Promise<T> - 代表一个异步操作的结果,最终完成时返回值的类型是T

    function fetchData(): Promise<string> {
        return fetch('https://api.example.com/data')
            .then(response => response.text());
    }
    
  3. Record<K, T> - 构造一个对象类型,其中键的类型是K,值的类型是T

    let person: Record<string, any> = { name: 'Alice', age: 30 };
    
  4. Partial<T> - 将T中的所有属性变为可选的。

    interface Todo {
        title: string;
        description: string;
    }
    
    function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
        return { ...todo, ...fieldsToUpdate };
    }
    
  5. Readonly<T> - 将T的所有属性设为只读。

    const config: Readonly<Config> = { port: 8080, host: 'localhost' };
    
  6. Pick<T, K extends keyof T> - 从T中选择出一组属性K来构造一个新的类型。

    interface Todo {
        title: string;
        description: string;
        completed: boolean;
    }
    
    type TodoPreview = Pick<Todo, 'title' | 'completed'>;
    
  7. Omit<T, K extends keyof T> - 从T中排除一组属性K来构造一个新的类型。

    type TodoInfo = Omit<Todo, 'description'>;
    
  8. Exclude<T, U> - 从T中排除可以赋值给U的类型。

    type T0 = Exclude<'a' | 'b' | 'c', 'a'>; // "b" | "c"
    
  9. Extract<T, U> - 提取T中可以赋值给U的类型。

    type T0 = Extract<'a' | 'b' | 'c', 'a' | 'f'>; // "a"
    
  10. NonNullable<T> - 从T中去除nullundefined

type T0 = NonNullable<string | number | undefined>; // string | number
  1. Parameters<T> - 获取函数类型T的参数类型组成的元组。

    function foo(arg1: string, arg2: number): void {}
    type T0 = Parameters<typeof foo>; // [string, number]
    
  2. ReturnType<T> - 获取函数类型T的返回值类型。

    function foo(): string { return "hello"; }
    type T0 = ReturnType<typeof foo>; // string
    
  3. ConstructorParameters<T> - 获取构造函数类型T的参数类型组成的元组。

    class C {
        constructor(x: number, y: string) {}
    }
    type T0 = ConstructorParameters<typeof C>; // [number, string]
    
  4. InstanceType<T> - 获取构造函数类型T实例的类型。

    class C {
        x = 0;
        y = '';
    }
    type T0 = InstanceType<typeof C>; // C
    

这些内置泛型极大地增强了TypeScript的表达能力和灵活性,使得开发者能够更加精确地定义类型,从而提高代码质量和可维护性。

Last Updated:
Contributors: 乙浒