typescript - 打字稿:是否有递归键?
问题描述
有没有办法让这样的代码编译并保证类型安全?
type ComplexObject = {
primitive1: boolean;
complex: {
primitive2: string;
primitive3: boolean;
}
};
interface MyReference {
myKey: keyof ComplexObject;
}
const works1: MyReference = {
myKey: "primitive1"
}
const works2: MyReference = {
myKey: "complex"
}
const iWantThisToCompile1: MyReference = {
myKey: "complex.primitive2" // Error: Type '"complex.primitive2"' is not assignable to type '"primitive1" | "complex"'.
}
const iWantThisToCompile2: MyReference = {
myKey: "complex['primitive3']" // Error: Type '"complex['primitive3']"' is not assignable to type '"primitive1" | "complex"'.
}
// const iDontWantThisToCompile1: MyReference = {
// myKey: "primitive2"
// }
// const iDontWantThisToCompile2: MyReference = {
// myKey: "primitive3"
// }
您可以在此处使用此代码。
解决方案
这可以通过TypeScript 4.1中的新模板文字类型和递归类型来实现。
属性和索引访问类型
这是一种超越单一级别的定义方法。可以使用比这更少的类型,但这种方法在其公共 API 中没有其他未使用的类型参数。
export type RecursiveKeyOf<TObj extends object> = {
[TKey in keyof TObj & (string | number)]:
RecursiveKeyOfHandleValue<TObj[TKey], `${TKey}`>;
}[keyof TObj & (string | number)];
type RecursiveKeyOfInner<TObj extends object> = {
[TKey in keyof TObj & (string | number)]:
RecursiveKeyOfHandleValue<TObj[TKey], `['${TKey}']` | `.${TKey}`>;
}[keyof TObj & (string | number)];
type RecursiveKeyOfHandleValue<TValue, Text extends string> =
TValue extends any[] ? Text :
TValue extends object
? Text | `${Text}${RecursiveKeyOfInner<TValue>}`
: Text;
仅限属性访问类型
如果您只需要属性访问,则要简单得多:
export type RecursiveKeyOf<TObj extends object> = {
[TKey in keyof TObj & (string | number)]:
TObj[TKey] extends any[] ? `${TKey}` :
TObj[TKey] extends object
? `${TKey}` | `${TKey}.${RecursiveKeyOf<TObj[TKey]>}`
: `${TKey}`;
}[keyof TObj & (string | number)];
解释和细分
export type RecursiveKeyOf<TObj extends object> = (
(
// Create an object type from `TObj`, where all the individual
// properties are mapped to a string type if the value is not an object
// or union of string types containing the current and descendant
// possibilities when it's an object type.
{
// Does this for every property in `TObj` that is a string or number
[TKey in keyof TObj & (string | number)]:
RecursiveKeyOfHandleValue<TObj[TKey], `${TKey}`>;
}
)[
keyof TObj & (string | number) // for every string or number property name
] // Now flatten the object's property types to a final union type
);
// This type does the same as `RecursiveKeyOf`, but since
// we're handling nested properties at this point, it creates
// the strings for property access and index access
type RecursiveKeyOfInner<TObj extends object> = {
[TKey in keyof TObj & (string | number)]:
RecursiveKeyOfHandleValue<TObj[TKey], `['${TKey}']` | `.${TKey}`>;
}[keyof TObj & (string | number)];
type RecursiveKeyOfHandleValue<TValue, Text extends string> =
// If the value is an array then ignore it, providing back
// only the passed in text
TValue extends any[] ? Text :
// If the value is an object...
TValue extends object
// Then...
// 1. Return the current property name as a string
? Text
// 2. Return any nested property text concatenated to this text
| `${Text}${RecursiveKeyOfInner<TValue>}`
// Else, only return the current text as a string
: Text;
例如:
// this type
{
prop: { a: string; b: number; };
other: string;
}
// goes to
{
prop: "prop" | "prop.a" | "prop.b";
other: "other";
}
// goes to
"prop" | "prop.a" | "prop.b" | "other"
推荐阅读
- c++ - 未定义对 log4cpp 的引用(GENIE 生成器安装错误消息)
- php - 关于php解析api返回的json问题
- opencart-module - 如何在 opencart 3 的我的帐户部分创建自定义选项卡?
- linux - arm linux系统调用中vector_swi()中使用的堆栈指针是如何初始化的?
- linux - crontab 无法删除文件夹
- reactjs - Google Drive上传文件rest api返回401错误
- cytoscape.js - 根据边的数据和目标的数据选择边
- node.js - 通过 node.js 将几何对象解析为 wkt
- c - 在行号出现中产生“3”的第一个索引的函数
- c++ - C++ clang 链接器命令失败,退出代码为 1