Typescript 泛型:如何从对象的另一个属性隐式派生该对象的属性的类型?

obi*_*ahn 5 generics types typescript typescript-generics conditional-types

我想在数组中键入对象,以便对象的一个​​属性(此处)隐式定义每个对象的foo另一个属性()的类型。bar

那可能吗?

type Foo<T> = {
  foo: T;
  bar: T;
};

const foos: Foo<any>[] = [ // <-- What to put here instead of "any"?
  { foo: "text", bar: "this is ok"}, 
  { foo: 666, bar: "this is NOT ok" }, // <-- should show error (must be of type number)
];
Run Code Online (Sandbox Code Playgroud)

在示例中foo具有bar相同的类型,在我的真实案例场景中我有:

type Column<TData, TValue> = {
  accessorFunction: (data:TData) => TValue,
  cell: (info:TValue) => any
}

const columns = [
  {
    accessorFunction: ()=> "test",
    cell: (info) => info   // <-- info should be of type string
]
Run Code Online (Sandbox Code Playgroud)

但我想这也有同样的打字问题。

jca*_*alz 5

(我将重点关注您的Column示例并忽略Foo,因为推理问题不同,并且您关心的Column不仅仅是Foo)。

从概念上讲,类似“对象的集合,其中每个对象都是我知道Column<D, V>的特定类型D,但对于某些V我不关心的类型”需要存在量化的泛型类型。(我想你需要知道D,因为如果你不知道,就没有办法打电话accessorFunction)。

但很少有语言支持此类类型,TypeScript 也不支持;有关相关功能请求,请参阅microsoft/TypeScript#14466 。如果我们确实有它们,你可以这样说Array<Column<D, exists V>>。但我们不这样做,所以我们不能。


有多种方法可以对存在类型进行编码。使用类数据结构通过通用回调反转控制流一般方法。泛型函数允许调用者指定类型参数,而实现者只知道它是某种类型。它看起来像这样:Promise

type SomeColumn<D> = <R>(cb: <V>(col: Column<D, V>) => R) => R;
Run Code Online (Sandbox Code Playgroud)

ASomeColumn<D>就像 aPromise<Column<D, ??>>then()方法。它接受回调,然后可能Column<D, V>对其所持有的底层证券调用回调。要将 a 转换Column<D, V>为 a SomeColumn<D>,我们可以使用辅助函数:

const someColumn = <D, V>(col: Column<D, V>): SomeColumn<D> => cb => cb(col);
Run Code Online (Sandbox Code Playgroud)

那么你的数组可能看起来像这样:

const columns = [
    someColumn({
        accessorFunction: (a: string) => "test",
        cell: (info) => info.toUpperCase()
    }),
    someColumn({
        accessorFunction: (a: string) => a.length,
        cell: info => info.toFixed(2)
    })
];
Run Code Online (Sandbox Code Playgroud)

columns是类型SomeColumn<string>。如果我们想处理该数组,我们必须添加一个嵌套回调。说吧,像这样:

const results = columns.map(
    sc => sc(
        col => col.cell(col.accessorFunction("hello"))
    )
)
// const results: any[]
console.log(results); // ["TEST", "5.00"]
Run Code Online (Sandbox Code Playgroud)

Column<string, V>请注意,我正在做我能在我不知道的地方做的唯一有用的事情V......那就是,调用col.cell(col.accessorFunction(someString)). 无论如何,results是一个输出数组cell()(您输入为any,所以我们有any[])。


这对于您的用例来说可能有点过分了。也许您关心的只是cell()方法输入的推断,并且您不介意是否columns是像 那样的数组Array<Column<string,string> | Column<string, number>>Promise如果是这样,您可以保留辅助函数,但只需从中删除任何类似的行为即可。它接受 aColumn<D, V>并返回 a Column<D, V>

const column = <D, V>(col: Column<D, V>) => col;

const columns = [
    column({
        accessorFunction: (a: string) => "test",
        cell: (info) => info.toUpperCase()
    }),
    column({
        accessorFunction: (a: string) => a.length,
        cell: info => info.toFixed(2)
    })
]; // okay
Run Code Online (Sandbox Code Playgroud)

但你会发现很难以编程方式处理这些:

const results = columns.map(
    col => col.cell(col.accessorFunction("hello")) // error!
    // -----------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // compiler doesn't know that this will always be the right type for col.cell
);
Run Code Online (Sandbox Code Playgroud)

那里也有解决方法,但它们很烦人,如果你小心正确地这样做,你也可以使用类型断言:

const results = (columns as Array<Column<string, any>>).map(
  col => col.cell(col.accessorFunction("hello"))
);
Run Code Online (Sandbox Code Playgroud)

Playground 代码链接