Typescript 缩小所有子树
这是关于使用3P库中的类型 我希望知道是否有一种快速的方法来缩小所有已知子树的范围,这样我就不需要单独声明每个感兴趣的字段和子字段了Typescript 缩小所有子树,typescript,Typescript,这是关于使用3P库中的类型 我希望知道是否有一种快速的方法来缩小所有已知子树的范围,这样我就不需要单独声明每个感兴趣的字段和子字段了 interface User { id: string; private?: Private; } interface Private { info?: Info; images?: ImageData[]; // ... } interface Info { name?: string; foo?: Foo; } // etc as
interface User {
id: string;
private?: Private;
}
interface Private {
info?: Info;
images?: ImageData[];
// ...
}
interface Info {
name?: string;
foo?: Foo;
}
// etc
async function client_list(id: string, parts: string[]) {
// retrieve some user data
// if successful, is guaranteed to return a known tree
// eg for parts == ["info"]
const user = {
id: id,
private: {
info: {
foo: {
bar: {
some_number: 42
}
}
}
}
} as User;
return user;
}
(async () => {
const user = await client_list("abcd", ["info"]);
user.private.info.foo.bar.some_number; // private, info, foo and bar are possibly undefined
// after somehow narrowing the subtree down to some_mumber, asserting each node is not undefined
user.private.info.foo.bar.some_number; // everything would be fine
})();
先谢谢你
Ps:我认为复制没有可选属性的接口可以很好地工作,但这意味着要复制很多接口。您可以使用递归和来获取对象类型T
和对应于该对象下的索引路径的KS
,并将其转换为一个新类型,其中路径KS
处的T
子树被替换为其自身版本,其中所有属性和子属性都是必需的,而不是可选的
但它并不特别漂亮
如果您只想将
T
转换成一个所有属性、子属性、子子属性等都是必需的而不是可选的版本,那么您可以非常轻松地做到这一点:
type DeepRequired<T> =
T extends Function ? T : { [K in keyof T]-?: DeepRequired<T[K]> }
您可以看到,DeepRequired
的name
和foo
属性都是必需的。name
的类型为string
,而foo
的类型为DeepRequired
,其属性为required,依此类推
请注意,DeepRequired
还将向下遍历其属性,使它们都是必需的,这可能不是您想要的。但我会考虑在这个问题的范围内做任何不同的事情。现在,这只是一个潜在的警告
当然,
DeepRequired
并不是你想要的;您需要类似于requiresbtree
的内容,其中只需要User
的private
属性,只需要其private
属性的info
属性,并且该属性的类型为“DeepRequired”
好吧,这里有一个方法可以做到这一点。首先,编写一个名为Expand
的类型函数非常有用,该函数将像{a:string}&{b:number}
这样的对象类型转换为等效的单个对象类型,如{a:string;b:number}
,并且还倾向于将嵌套类型函数(如Baz
)转换为具有明确写入属性的等效对象类型;有关更多信息,请参阅:
type Expand<T> = T extends infer U ? { [K in keyof U]: T[K] } : never;
看起来不错;private
属性是必需的,其类型a是必需的info
子树,但是images
属性仍然是可选的,这是需要的
现在,我们可以为您的
客户端列表
函数提供类型签名:
async function client_list<K extends keyof Private>(id: string, parts: K[]):
Promise<RequireSubtree<User, ["private", K]>> {
throw new Error("needs to be properly implemented");
}
看起来不错;info
属性有一个完全必需的子树,images
属性仍然可能是未定义的
将其与以下内容进行比较:
const otherUser = await client_list("efgh", ["images"]);
otherUser.private.info.foo.bar.some_number; // error, possibly undefined
otherUser.private.images.map(iD => iD.height.toFixed(2)); // okay
const thirdUser = await client_list("ijkl", ["images", "info"]);
thirdUser.private.info.foo.bar.some_number; // okay
thirdUser.private.images.map(iD => iD.height.toFixed(2)); // okay
const nilUser = await client_list("mnop", []);
nilUser.private.info.foo.bar.some_number; // error, possibly undefined
nilUser.private.images.map(iD => iD.height.toFixed(2)); // error, possibly undefined
我想这是你想要的行为,对吗
请注意,如果我认为您只打算使用一个或两个层次的子树路径,我可能不会建议使用完全递归的
RequiredSubtree
。相反,我可能只会使用DeepRequired
和一些手动输入private
和info
。但我假设这些只是示例,您的实际用例可能在某些任意深度具有所需的子树
最后,您应该考虑一个完全递归映射的条件类型是否值得这么复杂;你所说的手动版本很乏味,但概念上很简单;任何查看您的自定义接口的人都可能了解它们的功能以及在出现问题时如何调试,而查看RequiredSubtree
并试图找出其原因的人可能会遇到困难
请考虑修改这个问题中的代码,以便构成一个当它落入独立IDE时,清楚地显示出你所面临的问题(这样就没有语法或其他错误,除非这些问题是你的问题)。这将允许那些想要帮助您的人立即着手解决问题,而无需首先重新创建问题。它将使您得到的任何答案都可以根据定义良好的用例进行测试。谢谢您提醒我。我用一个工作片段更新了我的问题。谢谢,这非常有用。在尝试了你的建议之后,我会遵循你的建议
type UserWithDeepRequiredPrivateInfo = RequireSubtree<User, ["private", "info"]>
/* type UserWithDeepRequiredPrivateInfo = {
id: string;
private: {
images?: ImageData[] | undefined;
info: {
name: string;
foo: {
bar: {
some_number: number;
};
};
};
};
} */
async function client_list<K extends keyof Private>(id: string, parts: K[]):
Promise<RequireSubtree<User, ["private", K]>> {
throw new Error("needs to be properly implemented");
}
const user = await client_list("abcd", ["info"]);
/* const user: const user: {
id: string;
private: {
images?: ImageData[] | undefined;
info: {
name: string;
foo: {
bar: {
some_number: number;
};
};
};
};
} */
user.private.info.foo.bar.some_number; // okay
user.private.images.map(iD => iD.height.toFixed(2)); // error, possibly undefined
const otherUser = await client_list("efgh", ["images"]);
otherUser.private.info.foo.bar.some_number; // error, possibly undefined
otherUser.private.images.map(iD => iD.height.toFixed(2)); // okay
const thirdUser = await client_list("ijkl", ["images", "info"]);
thirdUser.private.info.foo.bar.some_number; // okay
thirdUser.private.images.map(iD => iD.height.toFixed(2)); // okay
const nilUser = await client_list("mnop", []);
nilUser.private.info.foo.bar.some_number; // error, possibly undefined
nilUser.private.images.map(iD => iD.height.toFixed(2)); // error, possibly undefined