目录
1️⃣ any、unknow和never
2️⃣ 函数重载
3️⃣ typeof和keyof(配合构建字典类型的Demo,巨好用‼️)
4️⃣ TS的条件类型
5️⃣ TS的声明合并
6️⃣ 枚举
7️⃣ 命名空间
8️⃣ 工具类型
一、any、unknow和never
any
any
类型表示一个值可以是任何类型。通常在不确定变量的类型,或者想要绕过TypeScript的类型检查的时候使用。但是使用any会导致类型安全的缺失,因为此时TypeScript不知道你在干什么,并且不会对类型为any的变量做任何类型检查。
滥用any的话会让TypeScript变成AnyScript,使得TS的使用没有意义。
unknow
unknown
类型表示一个值可以是任何类型,但是你不能对它进行任何操作,直到你确定了它的具体类型。unknow就像更安全的any,因为必须进行类型检查或者类型断言后才能使用这些值。
let value: unknown = 10;
value.toString(); // Code爆红:类型“unknown”上不存在属性“toString”。ts(2339)
(value as number).toString(); // Success
(value as string).toLocaleLowerCase(); // Success
if (typeof value === "number") {
value.toString(); // Success
}
if (typeof value === "string") {
value.toLocaleLowerCase();
}
never
never
类型表示那些永远不会发生的值的类型。它通常用于表示函数永远不会返回的情况(例如,函数内部总是抛出错误或进入无限循环)。
暂时还不知道有啥用嘿嘿。
二、TS函数重载
JavaScript是没有函数重载的,但是TypeScript有。
function add(a: string, b: string): string;
function add(a: number, b: number): number;
function add(a: any, b: any) {
if (typeof a === "string" && typeof b === "string") {
return a.concat(b);
}
if (typeof a === "number" && typeof b === "number") {
return a + b;
}
}
编译成JS后的代码也很简单:
function add(a, b) {
if (typeof a === "string" && typeof b === "string") {
return a.concat(b);
}
if (typeof a === "number" && typeof b === "number") {
return a + b;
}
}
三、typeof和keyof
在TypeScript中,typeof
可以用作类型查询运算符。当你将typeof应用于一个变量时,它会返回该变量的类型。例如:
const example = { name: "Alice", age: 30 };
type Person = typeof example;
const person: Person = {
name: "Bob",
age: 25,
gender: "male", // 对象字面量只能指定已知属性,并且“gender”不在类型“{ name: string; age: number; }”中。ts(2353)
};
keyof
用于获取类型中所有公共属性的联合类型。它通常与typeof一起使用,以从对象类型中提取键值。例如:
type PersonKeys = keyof Person; // "name" | "age"
const nameKey: PersonKeys = "name"; // Success
结合这两个运算符,可以在TypeScript中构建非常精确的类型系统。例如之前在实习的时候遇到过这样的需求:
const dictMap = {
college: "system_college",
semester: "system_semester",
courseProperty: "system_course_property",
} as const; // as const是必要的
// 需求(1):定义一个类型Dict,属性key的值只能取自dicMap的values,属性value的值为string
// 需求(2):dictMap在后续版本中可能改变,所以Dict的属性不能写死
interface Dict {
key: (typeof dictMap)[keyof typeof dictMap];
value: string
}
// 用法
const collegeDict: Dict = {
key: dictMap.college,
value: "College of Engineering"
}
四、TS的条件类型
在TS中可以根据条件从两种类型中选择一种类型。
type SpecialType<T> = T extends number ? bigint : string;
const bigNumber: SpecialType<number> = BigInt(10);
const stringValue: SpecialType<string> = "hello";
五、TS的声明合并
在 TypeScript 中,声明合并是指编译器会自动合并多个相同名称的类型声明为一个单一的声明。这种合并机制允许你从不同的地方定义类型、接口或命名空间,并将它们合并为一个整体,从而提供更灵活和强大的类型系统。
TypeScript 支持以下几种声明的合并:接口合并、命名空间合并、枚举合并。以接口合并为例:
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
// 合并后的 Box 接口:
// interface Box {
// height: number;
// width: number;
// scale: number;
// }
六、枚举
基本的使用如下:
enum Color {
Red = "Red",
Green = "Green",
Blue = "Blue",
}
const color = Color.Red;
console.log(color); // Output: "Red"
但是一番体验下来暂时还未找到必须用enum来编写的代码,很多时候普通的obj键值对就能搞定了 (枚举的使用确实很有写静态语言的感觉,就像C++)。例如我们可以用枚举来改写typeof和keyof中的代码:
enum DictMap {
college = "system_college",
semester = "system_semester",
courseProperty = "system_course_property",
}
interface Dict {
key: DictMap;
value: string;
}
const collegeDict: Dict = {
key: DictMap.college,
value: "College of Engineering",
};
那枚举存在的意义到底是什么呢?AI提示说:“(1)TypeScript编译后的JavaScript代码中,枚举可以被优化为对象或数组的形式,这可能比手动编写的键值对更高效;(2)枚举还可以作为命名空间来使用,避免与其他全局变量发生冲突,因为每个枚举都是一个独立的作用域,这有助于组织代码,让代码结构更清晰。”
但是一番操作下来,感觉这玩意最大的意义是在开发阶段与编译器的语法检测相配合,更早检测出潜在的语法错误。假设我们有一个大型项目,其中有两个不同的模块需要使用状态枚举,一个是用户模块(UserModule),另一个是系统模块(SystemModule)。这两个模块都需要一个表示“活动”状态的常量。
// UserModule.ts
const UserStatus = {
active: "user_active",
inactive: "user_inactive",
};
// SystemModule.ts
const SystemStatus = {
active: "system_active",
inactive: "system_inactive",
};
// 在其他地方使用
const userStatus = UserStatus.active;
const systemStatus = SystemStatus.active;
if (userStatus === systemStatus) {
// 此处的比较是“无意义的”,但VSCode不会爆红
console.log("User and System both are active.");
}
但是如果用枚举来操作就不一样了:
// UserModule.ts
enum UserStatus {
active = "user_active",
inactive = "user_inactive",
}
// SystemModule.ts
enum SystemStatus {
active = "system_active",
inactive = "system_inactive",
}
// 在其他地方使用
const userStatus = UserStatus.active;
const systemStatus = SystemStatus.active;
if (userStatus === systemStatus) {
// 这里编译器会报错:“此比较似乎是无意的,因为类型“UserStatus”和“SystemStatus”没有重叠。”
console.log("User and System both are active.");
}
总的来说,在TS项目中如果需要收集某些状态下的不同键值对,用enum比普通的obj(或者说map)更能发挥TS静态语言的优势。
七、命名空间
在JavaScript中提到“命名空间”,第一个联想到的就是闭包,闭包是可以用来解决变量名冲突的问题的。当然严格来说这不用扯到闭包,在JS中只要是函数,应该说都拥有独立的命名空间。事实上,在TS中如果使用命名空间,那编译出来的JS代码确实是用函数来封装的。 TS提供的命名空间解决的是全局命名冲突、代码组织混乱的问题。见下面的demo:
// 用户相关的命名空间
namespace UserStatus {
export const active = "user_active";
export const inactive = "user_inactive";
// TODO: do something
const count = 10;
for (let i = 0; i < count; i++) { console.log(i) }
} // 命名空间中的代码会立即执行,因为每个命名空间编译出来的都是一个JS中的立即执行函数。
// 系统相关的命名空间
namespace SystemStatus {
export const active = "system_active";
export const inactive = "system_inactive";
}
const userStatus = UserStatus.active;
八、工具类型
TypeScript中的工具类型是指那些可以操作和转换现有类型的类型。它们可以帮助开发者更灵活地构建类型,提高代码的可维护性和复用性。一些常见的工具类型包括Partial\Required、Readonly、Pick、Record、Exclude、Extract、Omit等。主包之前都不知道TS还有这玩意,但是看了它们的用法后马上觉得是很好用的东西,看下面的demo:
interface User {
name: string;
age: number;
email?: string;
}
// Partial
type PartialUser = Partial<User>; // 现在PartialUser的name和age属性都是可选的,email属性是可选的
const partialUser: PartialUser = { name: "John" };
// Required
type RequiredUser = Required<User>; // 现在RequiredUser的name、age和email属性都是必填的
const requiredUser: RequiredUser = {
name: "John",
age: 30,
email: "john@example.com",
};
// Readonly
type ReadonlyUser = Readonly<User>; // 现在ReadonlyPoint的name、age和email属性都是只读的
const readonlyUser: ReadonlyUser = {
name: "John",
age: 30,
email: "john@example.com",
};
readonlyUser.name = "Jane"; // Error: Cannot assign to 'name' because it is a read-only property.
// Pick
// 用法是:Pick<T, K>,即从T中选择一组属性K,返回一个新类型
type PickUser = Pick<User, "name" | "age">; // PickUser只拥有User的name和age属性
const pickUser: PickUser = { name: "John", age: 30 };
// Record
// 用法是:Record<K, T>,即创建一个键值对,其属性键为K,属性值为T
type UserRecord = Record<string, User>
const userA = { name: "boyiao", age: 12 }
const userRecord = { userA }
写在最后:为啥开这个专栏,纯粹是因为今天面试字节的时候,面试官问:JS和TS哪个用的多?我装杯:包是TS的🕶️。结果就是被拷打的无地自容,然后才意识到自己对TS的学习只停留在很浅薄的类型检测上。
所以这篇文章(这个专栏)持续更新,后续待补充的:泛型、高级类型、装饰器、模块、映射类型、。先吃宵夜去了😋。