繁体   English   中英

Typescript递归映射对象属性类型:对象和数组元素类型

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM