TypeScript 中 any、never、unknown、null & undefined 和 void 有什么区别?
any: 动态的变量类型(失去了类型检查的作用)。
never
: 永不存在的值的类型。例如:never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。
unknown
: 任何类型的值都可以赋给 unknown 类型,但是 unknown 类型的值只能赋给 unknown 本身和 any 类型。
null & undefined
: 默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给 number 类型的变量。当你指定了 --strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自。
void
: 没有任何类型。例如:一个函数如果没有返回值,那么返回值可以定义为void。
TypeScript 中 interface 可以给 Function / Array / Class(Indexable)做声明吗?
/* 可以 */
// 函数声明interface Say {
(name: string): viod;
}
let say: Say = (name: string):viod => {}
// Array 声明interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
// Class 声明interface PersonalIntl {
name: string
sayHi (name: string): string
}
ts中声明的时候哪些要小写,哪些要大写
在 TypeScript 中,标识符(变量名、函数名、类名等)是区分大小写的,因此对于声明的标识符,要注意以下规则:
- 小写标识符:通常用于声明变量、函数名以及对象属性。例如:
typescriptCopy code
let age: number = 30;
function greet(person: string): void {
// 函数实现
}
let myObject = { name: "John", city: "New York" };
- 大写标识符:在 TypeScript 中,一般用大写字母开头的标识符表示类型或类。例如:
typescriptCopy code
interface Person {
name: string;
age: number;
}
class MyClass {
// 类的成员和方法
}
- 驼峰命名法:这是一种常见的命名约定,表示标识符的首字母小写,后续单词首字母大写。在 TypeScript 中,通常用于命名变量、函数和对象属性。例如:
typescriptCopy code
let firstName: string = "John";
function calculateTotalPrice(): number {
// 函数实现
}
let myObject = { firstProperty: "value1", secondProperty: "value2" };
- 帕斯卡命名法:也称为大驼峰命名法,表示标识符的每个单词首字母都大写。在 TypeScript 中,通常用于命名类、接口和类型。例如:
typescriptCopy code
interface MyInterface {
// 接口成员
}
class MyClass {
// 类的成员和方法
}
type MyType = {
// 类型定义
};
总结起来,大部分声明的标识符(变量、函数、对象属性)使用小写和驼峰命名法,而类型和类的标识符则使用大写和帕斯卡命名法。这些命名约定有助于提高代码的可读性,并符合 TypeScript 社区的通用实践。
TypeScript 中的 this 和 JavaScript 中的 this 有什么差异?
TypeScript 中的 this 和 JavaScript 中的 this 在大部分情况下是相似的,但 TypeScript 引入了一些特性来更好地处理 this 的上下文。
JavaScript 中的 this: JavaScript 中的 this 是一个特殊的关键字,它表示当前执行上下文中的对象。在 JavaScript 中,this 的值取决于函数是如何被调用的,而不是函数被定义的位置。这导致在复杂的嵌套函数或事件处理程序中,this 的值可能会出现意外的变化或丢失上下文的问题。为了解决这个问题,通常需要使用 .bind()、.call() 或 .apply() 等方法来显式地绑定函数的上下文。
TypeScript 中的 this: TypeScript 中对 this 的处理更加严格和类型安全。在 TypeScript 中,箭头函数(Arrow Functions)可以捕获函数定义时的上下文,而不是在运行时根据调用方式决定 this 的值。这使得在箭头函数中使用 this 时更加可靠,因为它们不会改变 this 的指向。
例如,在 JavaScript 中:
const obj = {
name: "John",
greet: function() {
console.log("Hello, " + this.name);
}
};
const func = obj.greet;
func(); // 这里 this 的值会是 undefined 或全局对象(取决于严格模式)
而在 TypeScript 中,可以通过使用箭头函数解决这个问题:
const obj = {
name: "John",
greet: function() {
console.log("Hello, " + this.name);
}
};
const func = obj.greet;
func(); // 这里 this 的值会是 obj 对象中的 this,而不会出现 undefined 或全局对象的问题
总的来说,TypeScript 在处理 this 上更加健壮和可靠,而不需要显式地绑定函数上下文。箭头函数在 TypeScript 中是一种强有力的工具,可以避免 this 上下文错误。
TypeScript 中使用 Union Types 时有哪些注意事项?
属性或方法访问: 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法。
这里的.length在number类型上用不了,所以报错
function getLength(something: string | number): number {
return something.length;
}
// index.ts(2,22): error TS2339: Property 'length' does not exist on type >'string | number'.
// Property 'length' does not exist on type 'number'.
function getString(something: string | number): string {
return something.toString();
}
// 公共方法和属性可以访问
TypeScript 如何设计 Class 的声明?
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet(): string{
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
/**
在声明类的时候,一般类中都会包含:
构造函数、
对构造函数中的属性进行类型声明、
类中的方法。
*/
typeof赋值有什么意义?干嘛用?
在 TypeScript 中,使用 typeof
赋值的意义是获取某个变量或对象的类型,并将其作为一个类型进行使用。typeof
是 TypeScript 的类型操作符之一,用于获取变量或表达式的类型。
使用 typeof
有以下几个常见的用途:
-
类型声明简化:当我们想要使用一个已经存在的变量或对象的类型来声明新的类型时,可以使用
typeof
来获取其类型,从而简化类型声明。 const person = { name: "Alice", age: 30 }; type Person = typeof person; // 使用 typeof 获取 person 对象的类型
-
类型推导:有时候我们可能无法显式地给某个变量或对象定义类型,但希望 TypeScript 能够推导出其类型。这时可以使用
typeof
来获取该变量或对象的类型。 const age = 25; type AgeType = typeof age; // 使用 typeof 获取 age 变量的类型,AgeType 将被推导为 number 类型
-
与具体实例绑定:当我们定义了一个对象实例,并希望将其类型与某个变量绑定,可以使用
typeof
来获取对象实例的类型。typescriptCopy code const obj = { x: 10, y: 20 }; const point: typeof obj = { x: 5, y: 15 }; // 使用 typeof 获取 obj 对象的类型,并将其与 point 变量绑定
-
在类型中使用对象属性:有时候我们希望在一个类型中使用另一个对象的属性,可以通过
typeof
来获取该对象的类型,并在类型声明中使用其中的属性。 const person = { name: "Alice", age: 30 }; type PersonInfo = { name: string; info: typeof person }; // 使用 typeof 获取 person 对象的类型,并在类型声明中使用其属性
总的来说,typeof
在 TypeScript 中用于获取变量或对象的类型,这样可以在类型声明中更灵活地使用已有的类型,并且可以减少类型定义的重复性。使用 typeof
可以增强代码的可维护性和可读性,并且帮助 TypeScript 进行更准确的类型推导和类型检查。
什么是元组
一种特殊的数组类型,数组长度和每个位置的类型是固定的
// 声明一个元组类型
type MyTuple = [string, number, boolean];
// 定义一个元组变量
let myTuple: MyTuple = ["hello", 42, true];
// 访问元组的元素
console.log(myTuple[0]); // 输出 "hello"
console.log(myTuple[1]); // 输出 42
console.log(myTuple[2]); // 输出 true
TypeScript 中 type 和 interface 的区别?
相同点:
- 都可以描述 '对象' 或者 '函数'
- 都允许拓展(extends) 不同点:
- type 可以声明基本类型,联合类型,元组
- type 可以使用 typeof 获取实例的类型进行赋值
- 多个相同的 interface 声明可以自动合并 使用 interface 描述‘数据结构’,使用 type 描述‘类型关系’
关于使用 interface 描述‘数据结构’,使用 type 描述‘类型关系’,举例如下:
使用 interface 描述数据结构:
// 使用 interface 描述数据结构
interface Person {
name: string;
age: number;
address?: string;
}
// 定义一个对象实例符合 Person 接口的结构
const person: Person = {
name: "Alice",
age: 25,
address: "New York",
};
在这个例子中,我们使用 interface
关键字定义了一个接口 Person
,它描述了一个包含 name
和 age
属性的对象结构,address
属性是可选的。然后,我们创建了一个符合 Person
接口的对象实例 person
,其中包含了 name
和 age
属性。
使用 type 描述类型关系:
// 使用 type 描述类型关系
type Age = number;
type Name = string;
// 定义一个类型关系
type Person = {
name: Name;
age: Age;
};
// 定义一个对象实例符合 Person 类型的关系
const person: Person = {
name: "Bob",
age: 30,
};
在这个例子中,我们使用 type
关键字定义了两个类型 Age
和 Name
,它们分别表示 number
和 string
类型。然后,我们定义了一个类型 Person
,它通过使用 Name
和 Age
来描述了对象的结构。最后,我们创建了一个符合 Person
类型的对象实例 person
,其中包含了 name
和 age
属性。
TypeScript 中 ?.、??、!、!.、_、 等符号的含义?**
?. 可选链 遇到 null 和 undefined 可以立即停止表达式的运行。
?? 空值合并运算符
当左侧操作数为 null 或 undefined 时,其返回右侧的操作数,否则返回左侧的操作数。
! 非空断言运算符
x! 将从 x 值域中排除 null 和 undefined
!.
在变量名后添加,可以断言排除undefined和null类型
_ 数字分割符
分隔符不会改变数值字面量的值,使人更容易读懂数字 .e.g 1_101_324。
*
求幂
// 可选链示例
interface Person {
name: string;
address?: {
city: string;
postalCode?: string;
};
}
const defaultValue = 'default';
const userInput: string | undefined = undefined;
let myString: string | undefined = "Hello!";
const person: Person = {
name: 'Alice',
};
// 安全地访问深层嵌套属性
const city = person.address?.city;
console.log(city); // 输出: undefined
// 空值合并运算符示例
const result = userInput ?? defaultValue;
console.log(result); // 输出: 'default'
// 非空断言运算符示例
function printLength(str: string | undefined) {
// 使用非空断言运算符确保 str 不是 undefined 或 null
console.log(str!.length); // 输出: 6
}
// 断言运算符示例
const length: number = myString!.length;
console.log(length); // 输出: 6
// 数字分割符示例
const bigNumber = 1_000_000;
console.log(bigNumber); // 输出: 1000000
// 求幂符号示例
const powerResult = 2 ** 3;
console.log(powerResult); // 输出: 8 (2 的 3 次方)
简单介绍一下 TypeScript 模块的加载机制?
假设有一个导入语句 import { a } from "moduleA";
- 首先,编译器会尝试定位需要导入的模块文件,通过绝对或者相对的路径查找方式;
- 如果上面的解析失败了,没有查找到对应的模块,编译器会尝试定位一个
外部模块声明
(.d.ts);- 最后,如果编译器还是不能解析这个模块,则会抛出一个错误
error TS2307: Cannot find module 'moduleA'.
找d.ts的时候要写路径还是只需要写文件名
在 TypeScript 中查找外部模块声明文件(.d.ts)时,通常只需要写文件名,而不需要写路径。编译器会根据模块名去查找相应的声明文件。
假设有一个模块名为 "moduleA",编译器会按照以下步骤查找对应的声明文件:
- 首先,编译器会在当前文件所在目录下查找 "moduleA.d.ts" 文件。
- 如果当前文件所在目录下找不到 "moduleA.d.ts",则会向上级目录继续查找,直到找到声明文件或到达文件系统的根目录为止。
所以,通常情况下,您只需要写模块名 "moduleA",而不需要写完整的路径,编译器会根据模块名自动查找相应的声明文件。
简单聊聊你对 TypeScript 类型兼容性的理解?(不重要)
ts 类型兼容: 当一个类型 Y 可以赋值给另一个类型 X 时, 我们就可以说类型 X 兼容类型 Y。也就是说两者在结构上是一致的,而不一定非得通过 extends 的方式继承而来
接口的兼容性:X = Y
只要目标类型 X 中声明的属性变量在源类型 Y 中都存在就是兼容的( Y 中的类型可以比 X 中的多,但是不能少)
函数的兼容性:X = Y
Y 的每个参数必须能在 X 里找到对应类型的参数,参数的名字相同与否无所谓,只看它们的类型(参数可以少但是不能多。与接口的兼容性有区别,原因参考第 17 问)
协变、逆变、双变和抗变的理解?(不重要)
协变:X = Y Y 类型可以赋值给 X 类型的情况就叫做协变,也可以说是 X 类型兼容 Y 类型
typescript复制代码 interface X { name: string; age: number; } interface Y { name: string; age: number; hobbies: string[] } let x: X = { name: 'xiaoming', age: 16 } let y: Y = { name: 'xiaohong', age: 18, hobbies: ['eat'] } x = y
逆变:printY = printX
函数X 类型可以赋值给函数Y 类型,因为函数Y 在调用的时候参数是按照Y类型进行约束的,但是用到的是函数X的X的属性和方法,ts检查结果是类型安全的。这种特性就叫做逆变,函数的参数有逆变的性质。
typescript复制代码 let printY: (y: Y) => voidprintY = (y) => { console.log(y.hobbies) } let printX: (x: X) => voidprintX = (x) => { console.log(x.name) } printY = printX
双变(双向协变):X = Y;Y = X
父类型可以赋值给子类型,子类型可以赋值给父类型,既逆变又协变,叫做“双向协变”(ts2.x 之前支持这种赋值,之后 ts 加了一个编译选项 strictFunctionTypes,设置为 true 就只支持函数参数的逆变,设置为 false 则支持双向协变)
抗变(不变):
非父子类型之间不会发生型变,只要类型不一样就会报错
TypeScript 中对象展开会有什么副作用吗?
展开对象后面的属性会覆盖前面的属性; 仅包含对象自身的可枚举属性,不可枚举的属性将会丢失。
举例说明
在 TypeScript 中,对象展开确实会有一些副作用,正如您所提到的:
- 属性覆盖:当展开多个对象时,后面对象中的属性将会覆盖前面对象中相同名称的属性。这意味着如果两个对象中有相同的属性名,后面的对象中的属性值会覆盖前面的属性值。
- 不可枚举属性丢失:对象展开只会包含对象自身的可枚举属性,不可枚举的属性将会丢失。在 JavaScript 中,对象属性默认情况下是可枚举的,但是可以通过 Object.defineProperty() 方法将属性设置为不可枚举。
以下是一个示例来说明这些副作用:
typescriptCopy code
// 定义两个对象
const obj1 = { name: 'Alice', age: 30 };
const obj2 = { age: 25, gender: 'female' };
// 对象展开,后面的属性会覆盖前面的属性
const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // { name: 'Alice', age: 25, gender: 'female' }
// 定义一个包含不可枚举属性的对象
const objWithNonEnumerable = { id: 123 };
Object.defineProperty(objWithNonEnumerable, 'secret', {
value: 'topsecret',
enumerable: false, // 设置属性为不可枚举
});
// 对象展开,不可枚举的属性将丢失
const expandedObj = { ...objWithNonEnumerable };
console.log(expandedObj); // { id: 123 }
在这个示例中,obj1
和 obj2
分别包含两个对象,mergedObj
展开了这两个对象,并将后面的属性覆盖了前面的属性。同时,objWithNonEnumerable
包含一个不可枚举的属性 "secret",但在对象展开后,这个属性会丢失。
因此,在使用对象展开时,需要注意这些副作用,以确保不会意外覆盖属性或丢失重要的属性。
这个在js中会存在吗
是的,在 JavaScript 中,对象展开的行为与 TypeScript 中描述的一样,也会存在相同的副作用。JavaScript 中的对象展开语法与 TypeScript 中一致,因此在 JavaScript 中也会出现属性覆盖和不可枚举属性丢失的情况。
以下是相同的示例,在 JavaScript 环境中也会产生相同的结果:
javascriptCopy code
// 定义两个对象
const obj1 = { name: 'Alice', age: 30 };
const obj2 = { age: 25, gender: 'female' };
// 对象展开,后面的属性会覆盖前面的属性
const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // { name: 'Alice', age: 25, gender: 'female' }
// 定义一个包含不可枚举属性的对象
const objWithNonEnumerable = { id: 123 };
Object.defineProperty(objWithNonEnumerable, 'secret', {
value: 'topsecret',
enumerable: false, // 设置属性为不可枚举
});
// 对象展开,不可枚举的属性将丢失
const expandedObj = { ...objWithNonEnumerable };
console.log(expandedObj); // { id: 123 }
因此,不管是在 TypeScript 还是 JavaScript 中,对象展开的行为都是一致的,需要注意可能产生的副作用。
类型的全局声明和局部声明
在 TypeScript 中,确实存在全局声明和局部声明的概念,这与声明文件中是否包含 import 和 export 语句有关。
- 全局声明:当一个声明文件(.d.ts 文件)内不包含 import 和 export 语句时,其中声明的类型会成为全局声明。这意味着在整个项目中,无需导入该声明文件,就可以在任何地方使用其中声明的类型。
- 局部声明:相反,如果一个声明文件包含了 import 和 export 语句,其中声明的类型将成为局部声明。这意味着在使用这些类型时,需要在其他文件中显式导入这个声明文件。
下面通过一个示例来说明全局声明和局部声明的区别:
假设有两个 TypeScript 文件:
global-declarations.d.ts(全局声明):
// 没有 import 和 export 语句
declare interface Person {
name: string;
age: number;
}
declare function greet(name: string): void;
local-declarations.ts(局部声明):
// 包含 import 和 export 语句
import { Person } from './global-declarations';
export function sayHello(person: Person) {
console.log(`Hello, ${person.name}!`);
}
在上面的例子中,global-declarations.d.ts
文件没有包含 import 和 export 语句,所以其中声明的 Person
接口和 greet
函数会成为全局声明,无需在其他文件中导入就可以使用。
而在 local-declarations.ts
文件中,我们使用了 import { Person } from './global-declarations';
来导入 global-declarations.d.ts
文件中的 Person
接口。这使得 Person
接口在 local-declarations.ts
文件中成为局部声明,只有在该文件中才能使用,而在其他文件中使用 Person
接口时,也需要显式导入 global-declarations.d.ts
。
总结:全局声明不需要显式导入,可以在整个项目中直接使用;局部声明需要显式导入,只能在声明文件所在的文件或导入了该文件的文件中使用。
TypeScript 中同名的 interface 或者同名的 interface 和 class 可以合并吗?
同名的interface会自动合并,同名的interface和class会自动聚合。
举例说明
在 TypeScript 中,确实可以合并同名的接口(interface)或者同名的接口和类(class)。这种特性允许您将多个同名的接口或接口与类的定义合并为一个更大的定义。
-
同名接口的自动合并:
// 假设我们有两个同名的接口 interface Person { name: string; } interface Person { age: number; } // TypeScript 会自动将这两个同名接口合并为一个 const person: Person = { name: "Alice", age: 30, }; console.log(person); // { name: "Alice", age: 30 }
-
同名接口和类的自动聚合:
// 假设我们有一个同名接口和一个同名类 interface User { name: string; } class User { age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } } // TypeScript 会自动将同名接口和类合并为一个类 const user = new User("Bob", 25); console.log(user.name); // "Bob" console.log(user.age); // 25
在上述例子中,TypeScript 会自动将同名的接口和类合并成一个类,因此我们可以使用同一个名称 User
来表示该类的结构,包含类的属性和方法以及接口的属性。
需要注意的是,合并时有一些限制和规则,例如合并的接口必须在同一个作用域内,且不能有相同名称的非接口成员。另外,对于类的合并,类的构造函数只能在一个类声明中定义。
总结:在 TypeScript 中,同名的接口会自动合并为一个接口,同名的接口和类会自动聚合为一个类,这样可以更灵活地组织代码和类型定义。
如何使 TypeScript 项目引入并识别编译为 JavaScript 的 npm 库包?
选择安装 ts 版本,npm install @types/包名 --save;对于没有类型的 js 库,需要编写同名的.d.ts文件
举例说明
理解您的问题,您想了解如何在 TypeScript 项目中引入并正确识别编译为 JavaScript 的 npm 库包。通常情况下,TypeScript 项目可以轻松引用包含类型声明的 npm 库包,但对于没有提供类型声明的 JavaScript 库包,您需要做一些额外的工作,以确保 TypeScript 可以正确地使用它们。
以下是在 TypeScript 项目中引入 npm 库包的步骤:
-
安装 npm 包:您可以使用
npm
或yarn
等包管理器来安装所需的 npm 包。例如,通过运行以下命令安装一个名为example-package
的库包:npm install example-package --save
-
如果 npm 包本身提供了类型声明(通常在
@types
命名空间下),则无需额外配置,TypeScript 会自动识别并使用这些类型声明。 -
如果 npm 包没有提供类型声明,您可以通过安装与其名称相同但带有
@types/
前缀的 TypeScript 类型声明包,来为该包提供类型定义。例如,如果要为example-package
提供类型定义,可以运行以下命令:npm install @types/example-package --save-dev
这将安装与
example-package
对应的类型声明文件。注意将--save-dev
选项用于保存这些类型声明作为开发依赖项。 -
对于没有类型声明的 JavaScript 库包,如果没有相关的
@types
包,并且您无法找到社区中提供的类型声明文件,您可以手动编写一个与库包相同名称的.d.ts
文件。在这个文件中,您可以使用 TypeScript 类型语法为库包中的对象、函数和类添加类型声明。在 TypeScript 编译器处理项目时,它将查找并使用这些.d.ts
文件中的类型声明。例如,假设您使用的是一个没有类型声明的名为
example-package
的 JavaScript 库包,您可以创建一个名为example-package.d.ts
的文件,并在其中添加相关的类型声明,如下所示:declare module 'example-package' { export function someFunction(arg: string): void; export const someValue: number; // ... 添加其他类型声明 }
这样,TypeScript 将能够正确地识别
example-package
的类型,并在您的项目中进行类型检查和自动完成。
需要注意的是,手动编写类型声明可能会比较繁琐,并且对于复杂的库包,可能无法完全捕获其所有特性。在这种情况下,建议尽量寻找由社区维护的官方或第三方类型声明包。如果找不到相应的类型声明,您可以考虑自己编写一个简化版的类型声明,以满足您项目中的需求。
declare,declare global是什么?
declare的使用场景
好的,让我用一个具体的例子来说明 declare
关键字的用法。
假设我们有一个名为 externalLib.js
的 JavaScript 文件,它包含一个全局函数和一个全局变量,但是该文件并没有提供类型声明。
// externalLib.js
function externalFunction(x) {
return x * 2;
}
var externalVariable = "Hello, world!";
现在,我们有一个 TypeScript 文件 app.ts
,希望在其中使用 externalLib.js
中的函数和变量,并希望 TypeScript 能够正确识别它们的类型。
在这种情况下,我们可以使用 declare
关键字来告诉 TypeScript 编译器这些变量和函数是在其他地方已经定义的,不需要在当前文件中实际进行定义。
// app.ts
declare function externalFunction(x: number): number;
declare var externalVariable: string;
const result = externalFunction(5); // TypeScript 知道 externalFunction 是一个接收 number 类型参数并返回 number 类型的函数
console.log(result); // 输出:10
const message = externalVariable; // TypeScript 知道 externalVariable 是一个 string 类型变量
console.log(message); // 输出:Hello, world!
这样,我们可以在 TypeScript 代码中使用第三方 JavaScript 库或全局变量,并且通过类型声明告诉 TypeScript 编译器如何正确地处理它们,从而提供类型检查和类型推断的支持。这对于集成现有的 JavaScript 代码到 TypeScript 项目中非常有用。
declare global
declare global
用于声明全局命名空间中的自定义类型或接口。- 它可以让我们在整个项目中都能使用这个类型或接口,避免在每个文件中重复引入或声明。
- 这样可以提高代码的可维护性和可读性,同时简化在多个文件中共享全局类型的操作。
对 TypeScript 类中成员的 public、private、protected、readonly 修饰符的理解?
public
: 被此限定符修饰的成员是可以被外部访问,成员默认为public;
private
:只可以被类的内部访问;
protected
:只可以被类的内部及类的子类访问;
readonly
: 关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
keyof 和 typeof 关键字的作用?
keyof 索引类型查询操作符 获取索引类型的属性名,构成联合类型。
typeof
获取一个变量或对象的类型。
keyof
和 typeof
是 TypeScript 中的两个关键字,用于不同的用途:
-
keyof:
keyof
关键字用于获取一个类型的所有键(属性名)组成的联合类型。- 它适用于获取对象类型或接口类型的键,然后将这些键组合成一个联合类型,方便在泛型和类型操作中使用。
示例:
typescriptCopy code interface Person { name: string; age: number; city: string; } type PersonKeys = keyof Person; // 等同于:type PersonKeys = "name" | "age" | "city"
-
typeof:
typeof
关键字用于获取一个值的类型。- 它适用于获取变量、函数、类等值的类型,用于进行类型推断或类型注解。
示例:
typescriptCopy code const num = 10; type NumType = typeof num; // 等同于:type NumType = number function add(a: number, b: number): number { return a + b; } type AddFuncType = typeof add; // 等同于:type AddFuncType = (a: number, b: number) => number
总结:
keyof
用于获取类型的键组成的联合类型。typeof
用于获取值的类型。
数组定义的两种方式
type Foo= Array<string>;
interface Bar {
baz: Array<{ name: string, age: number}>
}
type Foo = string[];
interface Bar {
baz : { name: string, age: number }[]
}
简述工具类型 Exclude
、Omit
、Merge
、Intersection
、Overwrite
的作用。
Exclude<T, U> 从 T 中排除出可分配给 U的元素。
Omit<T, K>
的作用是忽略T
中的某些属性。
Merge<O1, O2>
是将两个对象的属性合并。
Compute<A & B>
是将交叉类型合并
Intersection<T, U>
的作用是取T
的属性,此属性同样也存在与U
。
Overwrite<T, U>
是用U
的属性覆盖T
的相同属性。
/**
* Exclude 工具类型示例
*/
type Colors = "red" | "blue" | "green" | "yellow";
type PrimaryColors = "red" | "blue";
type SecondaryColors = Exclude<Colors, PrimaryColors>;
// 结果为 SecondaryColors = "green" | "yellow"
// --------------------------------------------------
/**
* Omit 工具类型示例
*/
interface Person {
name: string;
age: number;
city: string;
}
type PersonWithoutAge = Omit<Person, "age">;
// 结果为 PersonWithoutAge = { name: string; city: string; }
// --------------------------------------------------
/**
* Merge 工具类型示例
*/
type Point2D = { x: number; y: number };
type Point3D = { z: number };
type Point3DWith2D = Merge<Point2D, Point3D>;
// 结果为 Point3DWith2D = { x: number; y: number; z: number }
// --------------------------------------------------
/**
* Intersection 工具类型示例
*/
type Shape = { name: string };
type Color = { color: string };
type ColoredShape = Intersection<Shape, Color>;
// 结果为 ColoredShape = { name: string; color: string }
// --------------------------------------------------
/**
* Overwrite 工具类型示例
*/
type Person = { name: string; age: number };
type Employee = { name: string; position: string };
type EmployeeInfo = Overwrite<Person, Employee>;
// 结果为 EmployeeInfo = { name: string; position: string; }
参考: