typescript 如何为嵌套对象属性获得更窄的类型推断?

qlzsbp2j  于 2023-03-19  发布在  TypeScript
关注(0)|答案(2)|浏览(200)

下面是我正在使用的模式库的一个简化示例(playground link):

type Column<T extends string = string, V = unknown> = {
    internalType: T
    value: V
}

type Table = {
    [column: string]: Column
}

type Row<T extends Table> = {
    data: {
        [C in keyof T]: T[C]['value']
    }
}

type NumberColumn = Column<'int', number>
type StringColumn = Column<'str', string>
type BooleanColumn = Column<'bool', boolean>
type ReferenceColumn<T extends Table = Table> = Column<'ref', Row<T>> & { table: T }

type FooTable = {
    active: BooleanColumn
    priority: NumberColumn
}

type BarTable = {
    name: StringColumn
    foo: ReferenceColumn<FooTable>
}

type PickColumns<T extends Table, ColumnType extends Column> = Pick<
    T,
    {
        [C in keyof T]: T[C] extends ColumnType ? C : never
    }[keyof T]
> & {
    [column: string]: ColumnType
}

function columnFromReferenceTable<
    T extends Table,
    RefCols extends PickColumns<T, ReferenceColumn> = PickColumns<T, ReferenceColumn>,
    RefColName extends keyof RefCols = keyof RefCols,
    RefCol extends RefCols[RefColName] = RefCols[RefColName],
    RefTable extends RefCol['table'] = RefCol['table'],
    RefTableColName extends keyof RefTable = keyof RefTable
>(referenceColumn: RefColName, referenceTableColumn: RefTableColName) {
    referenceColumn
    referenceTableColumn
}

columnFromReferenceTable<BarTable>('foo', 'active')    // Valid
columnFromReferenceTable<BarTable>('foo', 'priority')  // Valid
columnFromReferenceTable<BarTable>('foo', 'wrong')     // Shouldn't be valid

基本上,有些表模式可能包含引用其他表模式的列。目标是有一个接受两个字符串的函数,其中第一个字符串是一个表中引用列的名称,第二个字符串是被引用的另一个表中任何列的名称。
问题是:我无法正确约束第二个参数的字符串值。第一个参数约束正确,但第二个参数可以是任何字符串值,这是不正确的。
我试图尽可能地简化这个例子,而不是过于做作,我知道函数的类型签名是不必要的冗长,但希望它能帮助读者理解我是如何尝试导航类型的。

axr492tv

axr492tv1#

由于不支持部分类型推理,因此当您只使用一个泛型参数调用函数时,将使用其他泛型参数的默认值,正如您所观察到的,这将导致示例基本上没有类型检查。
我们通常绕过这个问题的方法是 * 咖喱 * 函数:

function columnFromReferenceTable<T extends Table>() {
    return function <
        RefCols extends PickColumns<T, ReferenceColumn> = PickColumns<T, ReferenceColumn>,
        RefColName extends keyof RefCols = keyof RefCols,
        RefCol extends RefCols[RefColName] = RefCols[RefColName],
        RefTable extends RefCol["table"] = RefCol["table"],
        RefTableColName extends keyof RefTable = keyof RefTable
    >(referenceColumn: RefColName, referenceTableColumn: RefTableColName) {
        referenceColumn;
        referenceTableColumn;
    };
}

再插入一对括号,我们就可以开始了:

columnFromReferenceTable<BarTable>()("foo", "active");
columnFromReferenceTable<BarTable>()("foo", "priority");
columnFromReferenceTable<BarTable>()("foo", "wrong"); // errors

Playground
P.S.您现在可以删除类型参数的默认值,它仍然可以工作。

wooyq4lh

wooyq4lh2#

正如另一个答案指出的,我的问题是由于TypeScript当前缺少类型参数partial type inference,因为BarTable作为类型参数传递,而没有其他参数,所以使用约束较少的默认类型。
我没有使用另一个答案提供的快速解决方案,而是选择了一个不同的解决方案,它通过将表作为对象传入来完全避免部分类型推理问题,这支持所有类型参数的完全推理:

function columnFromReferenceTable<
    T extends Table,
    RefCols extends PickColumns<T, ReferenceColumn> = PickColumns<T, ReferenceColumn>,
    RefColName extends keyof RefCols = keyof RefCols,
    RefCol extends RefCols[RefColName] = RefCols[RefColName],
    RefTable extends RefCol['table'] = RefCol['table'],
    RefTableColName extends keyof RefTable = keyof RefTable
>(table: T, referenceColumn: RefColName, referenceTableColumn: RefTableColName) {
    table
    referenceColumn
    referenceTableColumn
}

const barTable = {} as BarTable

columnFromReferenceTable(barTable, 'foo', 'active')    // Valid
columnFromReferenceTable(barTable, 'foo', 'priority')  // Valid
columnFromReferenceTable(barTable, 'foo', 'wrong')     // Shouldn't be valid

在我的图书馆里,table已经是对象了,所以这个设计提供了比咖喱更好的DX。

相关问题