简体   繁体   English

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

[英]Typescript generics: How to implicitly derive the type of a property of an object from another property of that object?

I want to type objects within an array, so that one property of the object (here foo ) implicitly defines the type for another property ( bar ) for each object seperately.我想在数组中键入对象,以便对象的一个​​属性(此处为foo )隐式地为每个对象分别定义另一个属性( bar )的类型。

Is that possible?那可能吗?

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)
];

In the example foo and bar have the same type, in my real case scenario I have:在示例中foobar具有相同的类型,在我的真实案例中,我有:

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
]

But I guess this has the same typing problem.但我想这有同样的打字问题。

This throws the intended error:这会引发预期的错误:

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

const foos: (Foo<string> | Foo<number> | Foo<boolean>)[] = [ // <-- What to put here instead of "any"?
    { foo: "text", bar: "this is ok" },
    { foo: 666, bar: 2323 }, // <-- should show error (must be of type number)
]

Unfortunately, this is not so easy to apply to your second example.不幸的是,这并不容易应用于您的第二个示例。
However, it is possible to force the manual specification of the type by enable "noImplicitAny" in the tsconfig.json :但是,可以通过在tsconfig.json中启用“noImplicitAny”来强制手动指定类型:

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

const column: (Column<any, string> | Column<any, number> | Column<any, boolean>)[] = [
    {
        accessorFunction: () => "test",
        cell: (info) => info  // <-- if you enable "noImplicitAny" you get an error here because you need to define the type
    }
]

(I'm going to focus on your Column example and ignore Foo , because the inference issues are different, and you care about Column more than Foo ). (我将专注于您的Column示例并忽略Foo ,因为推理问题不同,并且您更关心Column而不是Foo )。

Conceptually something like "a collection of objects, each of which is of type Column<D, V> for a particular D I know, but for some V I don't care about" requires existentially quantified generic types .从概念上讲,类似于“对象的集合,对于我知道的特定D ,每个对象的类型为Column<D, V> ,但对于我不关心的某些V ”需要存在量化的泛型类型 (I presume you need to know D , since, if you don't, there's no way to call accessorFunction ). (我认为您需要知道D ,因为如果您不知道,则无法调用accessorFunction )。

But few languages support such types, and neither does TypeScript;但是很少有语言支持这种类型,TypeScript 也不支持。 see microsoft/TypeScript#14466 for the relevant feature request.有关相关功能请求,请参阅microsoft/TypeScript#14466 If we did have them, you could say something like Array<Column<D, exists V>> .如果我们确实有它们,你可以说类似Array<Column<D, exists V>> But we don't, so we can't.但我们没有,所以我们不能。


There are ways to encode existential types.有一些方法可以对存在类型进行编码。 The general method to use a Promise -like data structure to invert control flow with generic callbacks.使用类似Promise的数据结构通过通用回调反转控制流的通用方法。 A generic function allows the caller to specify the type parameter while the implementer only knows that it's some type.泛型函数允许调用者指定类型参数,而实现者只知道它是某种类型。 It looks like this:它看起来像这样:

type SomeColumn<D> = <R>(cb: <V>(col: Column<D, V>) => R) => R;

A SomeColumn<D> is like a Promise<Column<D, ??>> 's then() method. SomeColumn<D>就像Promise<Column<D, ??>>then()方法。 It accepts a callback and then presumably calls the callback on the underlying Column<D, V> it's holding.它接受一个回调,然后大概调用它所持有的底层Column<D, V>的回调。 To turn a Column<D, V> into a SomeColumn<D> , we can use a helper function:要将Column<D, V>转换为SomeColumn<D> ,我们可以使用辅助函数:

const someColumn = <D, V>(col: Column<D, V>): SomeColumn<D> => cb => cb(col);

Then your array could look like this:然后您的数组可能如下所示:

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

That columns is of type SomeColumn<string> .columns的类型为SomeColumn<string> If we want to process the array, we have to add that one nested callback.如果我们想处理数组,我们必须添加一个嵌套回调。 Say, like this:说,像这样:

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

Note that I'm doing just about the only useful thing I can do with a Column<string, V> where I don't know V ... that is, calling col.cell(col.accessorFunction(someString)) .请注意,我正在做的只是我可以用Column<string, V>做的唯一有用的事情,我不知道V ......也就是说,调用col.cell(col.accessorFunction(someString)) Anyway, results is an array of cell() outputs (which you've typed as any , so we have any[] ).无论如何, results是一个cell()输出数组(您输入了any ,所以我们有any[] )。


That might be overkill for your use case.对于您的用例来说,这可能有点矫枉过正。 Maybe all you care about is inference of the cell() method's input, and you don't mind if columns is an array like Array<Column<string,string> | Column<string, number>>也许您关心的只是推断cell()方法的输入,并且您不介意columns是否是像Array<Column<string,string> | Column<string, number>>这样的数组。 Array<Column<string,string> | Column<string, number>> . Array<Column<string,string> | Column<string, number>> If so, you can keep the helper function but just strip out any Promise -like behavior out of it.如果是这样,您可以保留辅助函数,但只需从中删除任何Promise的行为。 It accepts a Column<D, V> and returns a Column<D, V> :它接受一个Column<D, V>并返回一个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

But you will find it difficult to process these programmatically:但是你会发现很难以编程方式处理这些:

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
);

There are workarounds there too, but they're annoying and you might as well just use a type assertion if you're careful to do so correctly:那里也有解决方法,但它们很烦人,如果你小心正确地这样做,你不妨只使用类型断言:

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

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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