实际上,我有一个类,它将一些方法扩展为数据库表中一行的"代理".问题是,我无法让类型脚本正确地推断可以根据表传递的键集(即相关表上的列).

请考虑以下示例:

type table1 = {id: number, col1: string, col2: string}
type table2 = {id: number, colA: number, colB: number}
type DBTable = table1 | table2
type DBColumnName<T extends DBTable> = keyof T
type DBColumnValue<T extends DBTable, C extends DBColumnName<T>> = T[C]

class ProxyDBRow<T extends DBTable> {
    data: T

    getColumn<C extends DBColumnName<T>>(name: C): DBColumnValue<T, C> {
            return this.data[name]
    }
}

// proxyComponent.svelte
export let proxy: ProxyDBRow<table1> | ProxyDBRow<table2>
export let attr: DBColumnName<table1> | DBColumnName<table2>

const gc = () => {
    proxy.getColumn(attr) // expression not callable error
}

换句话说,我有一个(Svelte)组件,我向该组件传递了一个代理和列名,并且我希望进行类型推断,以确定所请求的属性是该表上的有效列.

我觉得unions 并不是真正合适的解决方案,但我不知道什么才是.我在文档中查看了"条件类型",但我似乎真的无法让它正常工作.我需要它来根据代理的类型推断attr的实际类型,我只是想不出如何正确地编写表达式.

此外,我还try 将代理的类型注释为ProxyDBRow<table1 | table2>,但这也不能正常工作:表的联合只允许所有表共有的键,因此,例如,如果传入Table1的代理检测到将‘col1’传递到getColumn方法中的内容,则会引发错误,因为Table2没有同名的键.

推荐答案

问题在于

let proxy: ProxyDBRow<Table1> | ProxyDBRow<Table2>
let attr: DBColumnName<Table1> | DBColumnName<Table2>

每个变量都是union type,但它们彼此独立.比方说,proxy可以是ProxyDBRow<Table2>,而attrDBColumnName<Table1>;例如:

let proxy = new ProxyDBRow<Table1>();
let attr = "colB";

在这种情况下,您不希望编译器允许proxy.getColumn(attr),因此出现错误是有充分理由的.


你想代表proxyattr作为某种形式的correlated个unions .但TypeScrip实际上并不直接支持相关的unions .你实际上可以表达这种关系:

declare let [proxy, attr]:
    [ProxyDBRow<Table1>, DBColumnName<Table1>] |
    [ProxyDBRow<Table2>, DBColumnName<Table2>]

但你也会遇到同样的问题:

proxy.getColumn(attr); // still error

microsoft/TypeScript#30581中描述了缺乏对相关联合类型的直接支持.


解决这些问题的建议方法是从unions 转向genericsunions .根据用例的不同,您可能需要进行相当大的重构,如microsoft/TypeScript#47109中所述.

但在这种情况下,我们不需要做出太大改变.我只给出了依赖于泛型类型参数constrainedDBTableproxyattr类型:

function gcFor<T extends DBTable>(proxy: ProxyDBRow<T>, attr: DBColumnName<T>) {
    return () => {
        proxy.getColumn(attr); // okay
    }
}

这是可行的,因为无论T是什么,proxy都有一个getColumn()方法,该方法接受类型DBColumnName<T>的参数,与attr匹配.

gcFor()的呼叫者将看到一些针对不匹配的保护:

const gc1 = gcFor(new ProxyDBRow<Table1>(), "col2"); // okay
const gc2 = gcFor(new ProxyDBRow<Table2>(), "colB"); // okay
const gcOops = gcFor(new ProxyDBRow<Table1>(), "colB"); // error

Playground link to code

Typescript相关问答推荐

从接口创建类型安全的嵌套 struct

更新:Typescript实用程序函数合并对象键路径

为什么从数组中删除一个值会删除打字错误?

为什么TypeScript假设文档对象始终可用?

如何通过TypeScript中的工厂函数将区分的联合映射到具体的类型(如类)?

如何根据特定操作隐藏按钮

如何在深度嵌套的Reaction路由对象中隐藏父级?

如何实现异构数组内函数的类型缩小

部分更新类型的类型相等

垫表页脚角v15

返回嵌套props 的可变元组

17个Angular 的延迟观察.如何在模板中转义@符号?

如果判断空值,则基本类型Gaurd不起作用

如何从JWT Reaction中提取特定值

打字:注解`是字符串`而不是`布尔`?

TypeScript中的这些条件类型映射方法之间有什么区别?

React中的效果挂钩在依赖项更新后不会重新执行

我可以在 Typescript 中重用函数声明吗?

如何使用动态生成的键和值定义对象的形状?

为什么我们在条件语句中使用方括号[]?