[英]Typescript recursively mapping object property types: object and array element types
更新1 - >见下文
给出以下接口
interface Model {
bla: Child1;
bwap: string;
}
interface Child1 {
blu: Child2[];
}
interface Child2 {
whi: string;
}
我正在尝试编写一个基本上像这样使用的函数:
let mapper = path<Model>("bla")("blu");
let myPropertyPath = mapper.path; //["bla", "blu"]
函数调用x仅接受在对象层次结构中在级别x处找到的类型的键的参数。
我有一个这样的工作版本,灵感来自RafaelSalguero在这里找到的帖子,它看起来像这样:
interface IPathResult<TRoot, TProp> {
<TSubKey extends keyof TProp>(key: TSubKey): IPathResult<TRoot, TProp[TSubKey]>;
path: string[];
}
function path<TRoot>() {
return <TKey extends keyof TRoot>(key: TKey) => subpath<TRoot, TRoot, TKey>([], key);
}
function subpath<TRoot, T, TKey extends keyof T>(parent: string[], key: TKey): IPathResult<TRoot, T[TKey]> {
const newPath = [...parent, key];
const x = (<TSubKey extends keyof T[TKey]>(subkey: TSubKey) => subpath<TRoot, T[TKey], TSubKey>(newPath, subkey)) as IPathResult<TRoot, T[TKey]>;
x.path = newPath;
return x;
}
这里有更多的上下文来看看我要去哪里:我正在编写一个与(p)反应一起使用的绑定库。 从Abraham Serafino的指南中汲取灵感,在这里找到:他使用普通的虚线来描述一条路径; 这样做时我想要类型安全。 因此,一个IPathResult<TRoot, TProp>
实例可以读取这样的:这让我从开始的一条路径TRoot
导致任何财产俯视型线TProp
。
其用法的一个例子是(没有实现细节)
<input type="text" {...bind.text(s => s("bwap")) } />
其中s
参数是path<TState>()
和TState = Model
。 bind.text
函数将返回一个value
属性和一个onChange
处理程序,它将调用组件的setState
方法,设置通过我们映射到新值的路径找到的值。
这一切都很有效,直到你在混合中抛出数组属性。 假设我想为Child1
接口上的blu
属性中找到的所有元素渲染输入。 我想做的是写这样的东西:
let mapper = path<Model>("bla"); //IPathResult<Model, Child1>
for(let i = 0; i < 2; i++) { //just assume there are 2 elements in that array
let myPath = mapper("blu", i)("whi"); //IPathResult<Model, string>, note: we're writing "whi", which is a key of Child2, NOT of Array<Child2>
}
因此,映射器调用会有一个重载,它为数组TProp
提供额外的参数,指定要绑定的数组中元素的索引,以及最重要的部分: 下一个映射器调用需要数组元素类型的键,不是数组类型本身!
所以将其插入到实际的绑定示例中,这可能会变成:
<input type="text" {...bind.text(s => s("bla")("blu", i)("whi")) } />
所以这就是我被困住的地方:即使忽视实际的实现,我如何在IPathResult<TRoot, TProp>
接口中定义该重载? 以下是它的要点,但不起作用:
interface IPathResult<TRoot, TProp> {
<TSubKey extends keyof TProp>(key: TSubKey): IPathResult<TRoot, TProp[TSubKey]>;
<TSubKey extends keyof TProp>(key: TSubKey, index: number): IPathResult<TRoot, TProp[TSubKey][0]>;
path: string[];
}
Typescript不接受TProp[TSubKey][0]
部分,说type 0 cannot be used to index type TProp[TSubKey]
。 我想这是有道理的,因为打字系统无法知道TProp[TSubKey]
可以这样索引,它根本就没有定义。
如果我写这样的界面:
interface IArrayPathResult<TRoot, TProp extends Array<TProp[0]>> {
<TSubKey extends keyof TProp[0]>(key: TSubKey): IPathResult<TRoot, TProp[0][TSubKey]>;
path: string[];
}
这允许我实际输入:
const result: IArrayPathResult<Model, Child2[]> = null;
result("whi"); //woohoow!
当然,这本身就是一个毫无意义的例子,但只是表明可以获得返回的数组元素类型。 但是TProp
现在被定义为始终是一个数组,显然情况并非如此。
我觉得我已经非常接近这一点了,但把它们放在一起就是在逃避我。 有任何想法吗?
重要提示 :没有实例参数来推断类型,并且不应该有任何!
更新1:
使用带有条件类型的Titian 解决方案 ,我可以使用我想要的语法。 但是,某些类型信息似乎已丢失:
const mapper = path<Model>();
const test: IPathResult<Model, string> = mapper("bla"); //doesn't error!
这应该不起作用,因为mapper("bla")
调用返回IPathResult<Model, Child1>
,它不能分配给IPathResult<Model, string>
! 如果你删除IPathResult
接口中的2个方法重载之一,上面的代码会正确地出错,说“类型'Child1'不能分配给'string'类型。”
很近! 还有什么想法吗?
您可以使用条件类型来提取item元素,并确保仅使用数组键调用数组版本:
type KeysOfType<T, TProp> = { [P in keyof T]: T[P] extends TProp? P : never}[keyof T];
type ElementTypeIfArray<T> = T extends Array<infer U> ? U : never
interface IPathResult<TRoot, TProp> {
<TSubKey extends KeysOfType<TProp, Array<any>>>(key: TSubKey, index: number): IPathResult<TRoot, ElementTypeIfArray<TProp[TSubKey]>>;
<TSubKey extends keyof TProp>(key: TSubKey): IPathResult<TRoot, TProp[TSubKey]>;
path: string[];
}
let mapper = path<Model>()("bla")("blu", 1)("whi"); // works
let mapper2 = path<Model>()("bla", 1) // error bla not an array key
更新
不确定为什么ts认为IPathResult<Model, string>
与IPathResult<Model, Child1>
IPathResult<Model, string>
兼容,如果我删除了任何重载,它确实报告了兼容性错误。 也许这是一个编译器错误,如果我有机会,我可能会在以后调查它。 最简单的方法是向IPathResult
添加一个字段,该字段直接使用TProp
来确保不兼容:
interface IPathResult<TRoot, TProp> {
<TSubKey extends KeysOfType<TProp, Array<any>>>(key: TSubKey, index: number): IPathResult<TRoot, ElementTypeIfArray<TProp[TSubKey]>>;
<TSubKey extends keyof TProp>(key: TSubKey): IPathResult<TRoot, TProp[TSubKey]>;
path: string[];
__: TProp;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.