什么是类型联动
类型联动是指在一个对象中,当某个属性的值发生变化时,其他属性的类型也随之变化。这种机制使得类型系统更加灵活和强大,能够更好地描述复杂的数据结构。
假设有这样一个类型:
type Component = {
type: 'setUserName' | 'setUserList' | 'steUserInfo';
data: string|string[]|{ test: string };
}
类型联动,指的是当 type 的值发生变化时,data的类型也跟着变化,比如当type为setUserName时,可能希望data为string,而当type为setUserList时,则希望data为string[]。
编码实战
不同type不同Data数据类型
期望:
当 type = 'setUserName' 时,data的类型是 string
当 type = 'setUserList' 时, data 的类型是 string[]
当 type = 'steUserInfo' 时, data 的类型是 { test: string }
type DataObj = {
setUserName: string;
setUserList: string[];
steUserInfo: { name: string };
};
type Data = {
[T in keyof DataObj]: {
type: T;
data: DataObj[T];
};
}[keyof DataObj];
const data1: Data = {
type: 'user',
data: 'tds',
};
const data2: Data = {
type: 'setUserName',
data: ['tds'],
};
console.log(data1, data2);
问题分析
在上述代码中,data1 和 data2 的初始化存在问题:
- data1 的 type 属性是 'setUserName',这是合法的,但 data 属性应该是一个字符串。
- data2 的 type 属性是 'setUserList',这是合法的,但 data 属性应该是一个字符串数组。
data1报错如下
data2报错如下
解决方案
修正后的代码如下:
const data1: Data = {
type: 'setUserName',
data: 'tds',
};
const data2: Data = {
type: 'setUserList',
data: ['tds'],
};
console.log(data1, data2);
知识剖析
核心代码
type Data = {
[T in keyof DataObj]: {
type: T;
data: DataObj[T];
};
}[keyof DataObj];
代码解析
定义 DataObj 类型别名
首先定义了一个名为 DataObj 的类型别名,它描述了一个对象结构:
type DataObj = {
setUserName: string;
setUserList: string[];
steUserInfo: { name: string };
};
setUserName是一个字符串。setUserList是一个字符串数组。steUserInfo是一个包含name字段的对象,其中name是一个字符串。
定义Data 映射类型
接着定义了另一个名为 Data 的类型,这是一个映射类型,基于 DataObj 的键生成新的类型结构:
type Data = {
[T in keyof DataObj]: {
type: T;
data: DataObj[T];
};
}[keyof DataObj];
这里的关键点是:
-
keyof DataObj获取DataObj的所有键名。 -
[T in keyof DataObj]遍历DataObj的每一个键T。 -
{[T in keyof DataObj]:{ type: T; data: DataObj[T]; }}对于每个键T,创建一个新的类型成员,该成员是一个对象,包含两个属性:-
type: T:表示当前键的名字。 -
data: DataObj[T]:表示与当前键关联的数据类型。 -
此步骤生成的映射类型如下所示
type DataTemp = { setUserName: { type: 'setUserName'; data: string; }; setUserList: { type: 'setUserList'; data: string[]; }; steUserInfo: { type: 'steUserInfo'; data: { name: string; }; }; };
-
-
最后的
[keyof DataObj]将前面生成的映射类型转换为一个联合类型,包含了所有可能的键值对对象。-
此步骤生成的联合类型如下所示
type Data = { type: "setUserName"; data: string; } | { type: "setUserList"; data: string[]; } | { type: "steUserInfo"; data: { name: string; }; }
-
涉及到的知识点
- 泛型[3]
- keyof 关键字[4]
- in 关键字[4]
- 索引类型与映射类型[3]
1. 泛型
泛型(Generics)允许你在定义函数、接口或类时使用一个或多个类型参数。这样做的好处是可以在不指定具体类型的情况下编写可重用的代码,同时保持类型安全。
语法
function identity<T>(arg: T): T {
return arg;
}
<T>表示这是一个类型参数。T可以是任何类型,当调用identity函数时,可以传入具体的类型来替代T。
例子
let output = identity<string>("myString"); // output 的类型为 string
let output2 = identity<number>(42); // output2 的类型为 number
2. keyof 关键字
keyof 关键字用于获取一个对象类型的键名的联合类型。这对于构建映射类型非常有用。
语法
type Keys = keyof { a: number, b: string };
// Keys 的类型为 "a" | "b"
keyof会提取出{ a: number, b: string }对象的所有键名,并形成一个联合类型"a" | "b"。
例子
interface Person {
name: string;
age: number;
}
type PersonKeys = keyof Person; // "name" | "age"
3. in 关键字
在 TypeScript 中,in 关键字用于遍历对象的键。它通常与映射类型结合使用,以生成新的类型结构。
语法
type MappedTypeWithNewProperties<T> = {
[K in keyof T]: K extends string ? T[K] : never;
};
[K in keyof T]遍历T的所有键K。K extends string ? T[K] : never是一个条件类型,表示如果K是字符串,则类型为T[K];否则为never。
例子
type Person = {
name: string;
age: number;
};
type MappedPerson = {
[K in keyof Person]: Person[K];
};
// MappedPerson 的类型为 { name: string; age: number; }
4. 索引类型
索引类型允许你通过键从一个对象中提取出相应的属性类型。这在处理动态键名时非常有用。
语法
interface StringArray {
[index: number]: string;
}
const myArray: StringArray = ["a", "b", "c"];
const secondElement: string = myArray[1]; // 类型为 string
[index: number]: string定义了一个索引签名,表示数组中的每个元素都是字符串。
例子
interface Dictionary {
[key: string]: boolean;
}
const options: Dictionary = {
isFlagged: true,
isReadOnly: false,
};
const flag: boolean = options["isFlagged"]; // 类型为 boolean
5. 映射类型
映射类型是一种基于现有类型的键来生成新的类型的方式。它们通常用于自动地将一种类型的属性转换成另一种类型。
语法
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
[Property in keyof Type]遍历Type的所有键Property。boolean是新类型中每个属性的类型。
例子
interface FeatureConfig {
darkMode: () => void;
newFeature: () => void;
}
type FeatureOptions = OptionsFlags<FeatureConfig>;
// FeatureOptions 的类型为 { darkMode: boolean; newFeature: boolean; }
总结
- 泛型:允许你在定义函数、接口或类时使用类型参数,从而编写更通用和可重用的代码。
- keyof 关键字:用于获取一个对象类型的键名的联合类型。
- in 关键字:用于遍历对象的键,通常与映射类型结合使用。
- 索引类型:允许你通过键从一个对象中提取出相应的属性类型。
- 映射类型:基于现有类型的键来生成新的类型,通常用于自动地将一种类型的属性转换成另一种类型。
这些概念是 TypeScript 中高级类型系统的重要组成部分,能够帮助你编写更加灵活和类型安全的代码。