我想有Columns<T>个类型,这将引用它自己的属性之一,但与不同的T类型.

Columns<T>ColumnDefinitionType<T>联合类型的array.

type Columns<T> = ColumnDefinition<T>[];

type ColumnDefinition<T> = SubgridColumn<T> | Column<T>;

Column<T>是具有一个属性的对象类型

type Column<T> = {
    render: (item: T, rowIndex: number) => React.ReactNode;
};

SubgridColumn<T>有两个属性

type SubgridColumn<T> = {
    subgridItems: ((item: T, rowIndex: number) => any[]) | any[];
    subgridColumns: Columns<any>;
};

目前,subgridItems可以是任何类型的数组,也可以是返回任何类型数组的函数,而subgridColumnsColumns<any>类型.

我想输入Subgrid<T, N>,其中N替换任何类型

type SubgridColumn<T, N> = {
    subgridItems: ((item: T, rowIndex: number) => N[]) | N[];
    subgridColumns: Columns<N>;
};

此类型的用法为

type Cargo = { locationFrom: string; models: Model[] };
type Model = { modelName: string; parts: Part[] };
type Part = { partName: string; partPrice: number };
type UsageCountry = { countryName: string; contintent: string };

const countries: UsageCountry[] = [
    { countryName: 'Ireland', contintent: 'Europe' },
    { countryName: 'USA', contintent: 'North America' },
];

const cargos: Cargo[] = [
    {
        locationFrom: 'London',
        models: [
            {
                modelName: 'Audi',
                parts: [
                    { partName: 'Spoiler', partPrice: 599 },
                    { partName: 'Wheel', partPrice: 199 },
                ],
            },
            { modelName: 'Subaru', parts: [{ partName: 'Tint', partPrice: 99 }] },
        ],
    },
    { locationFrom: 'Paris', models: [{ modelName: 'VW', parts: [{ partName: 'Muffler', partPrice: 159 }] }] },
];

波纹管是类型为Columns<Cargo>的柱.它是一个由ColumnDefinition<Cargo>组成的数组,可以是Column<Cargo>SubgridColumn<Cargo>.当它是后一种类型时,我想让subgridColumns从subgridItems类型推断类型. 因此,如果subgridItems返回类型为Model[],则subgridColumns应为Columns<Model>类型

const columns: Columns<Cargo> = [
    { render: cargo => cargo.locationFrom },
    {
        subgridItems: cargo => cargo.models,
        subgridColumns: [
            { render: model => model.modelName },
            {
                subgridItems: model => model.parts,
                subgridColumns: [{ render: part => part.partName }, { render: part => part.partPrice }],
            },
            {
                subgridItems: model => (model.modelName === 'Subaru' ? countries : []),
                subgridColumns: [
                    {
                        render: country => country.countryName,
                    },
                ],
            },
        ],
    },
];

您还可以看到,当使用Columns<Model>类型时,返回与ModelCargo类型无关的UsageCountry类型的国家/地区数组,因此基本上不应该将返回subgridItems的类型(或subgridItems类型,如果它不是函数,而是数组)绑定到Columns<T>中的类型T的属性

如何实现这一点,因为当前设置问题是返回类型subgridItems始终是Any,使用起来不安全.

推荐答案

所以你有一个像type SubgridColumn<T, N> = ⋯这样的类型,你想写下

type ColumnDefinition<T> = SomeSubgridColumn<T> | Column<T>;

其中,SomeSubgridColumn<T>是你不知道或不关心的some类型NSubgridColumn<T, N>.


当你发现自己需要一个像F<N>这样的类型来代替你不知道的some类型N,那么你就可以 Select 所谓的existentially quantified generics(也称为existential types).您想要说"有exists个类型N,因此结果类型是F<N>".但是,像大多数有generics的语言一样,TypeScrip只有universally-quantified generics个字符,这意味着F<N>必须使用for all个字符N.在microsoft/TypeScript#14466有一个长期开放的针对存在词类型的功能请求,但目前还不直接支持它们.

因此,如果你想要一个存在主义类型,你需要想办法绕过它.


最简单的方法是只使用the any type作为类型参数来替换存在泛型:

type SomeSubgridColumn<T> = SubgridColumn<T, any>

这与您最初的SubgridColumn版本相同.正如我所说,这是非常容易的.但它不能准确地表示存在类型,而且它也不是类型安全的.


在另一端,你可以使用通用泛型类型,通过一个类似Promise的 struct ,encode个存在泛型类型. 这个 idea 是,你得到的不是F<N>,而是一个接受F<N>回调的函数.你不能直接访问F<N>值,只有一个包含它的黑盒子.因为你不知道N是什么,所以你必须确保你的回调对all N有效. 当你改变数据流的方向时,共通性和共通性是彼此的,就像unionsintersections是彼此的共通性一样. (事实上,你可以把存在看作是一个"无限的联合",而普遍是一个"无限的交叉".

下面是它看起来的样子:

type SomeSubgridColumn<T> =
    <R>(cb: <N>(sc: SubgridColumn<T, N>) => R) => R;

所以如果我给你一个SomeSubgridColumn<T>,你可以给它一个回调,对于所有可能的N,它必须接受SubgridColumn<T, N>,它返回任何你想要的类型.当你这样做的时候,你会得到一个R类型的值.因此,您将回调放入黑框中,对隐藏的值运行回调,然后将结果传递给您.

SubgridColumn<T, N>放进SomeSubgridColumn<T>的盒子里很容易:

const someSubgridColumn = <T, N>(
    sc: SubgridColumn<T, N>
): SomeSubgridColumn<T> => cb => cb(sc);

并且您需要相应地包装您的数据:

const columns: Columns<Cargo> = [
    { render: cargo => cargo.locationFrom },
    someSubgridColumn({
        subgridItems: cargo => cargo.models,
        subgridColumns: [
            { render: model => model.modelName },
            someSubgridColumn({
                subgridItems: model => model.parts,
                subgridColumns: [
                    { render: part => part.partName },
                    { render: part => part.partPrice }],
            }),
            someSubgridColumn({
                subgridItems: model => (
                    model.modelName === 'Subaru' ? countries : []
                ),
                subgridColumns: [
                    {
                        render: country => country.countryName,
                    },
                ],
            }),
        ],
    }),
];

另一方面,消费SomeSubgridColumn<T>有点复杂,就像消费Promise一样.它在一个盒子里,你必须给它一个回调.例如:假设您想要取一个Columns<Cargo>,并将每个元素映射到它所具有的数字subgridColumns.用你原来的 struct ,你可能会写下

columns.map(cd => "render" in cd ? 0 : cd.subgridColumns.length);

但你不能这样做,因为cd不能直接得到subgridColumns.它在盒子里.所以你必须写下:

columns.map(cd => "render" in cd ? 0 : cd(sc => sc.subgridColumns.length));

这很管用.

这种方法比any的版本更复杂,但它的优点是实际上是类型安全的.


最后,重要的是要注意,如果事实证明你不关心的N只可能是一个相当小的有限类型集合中的一个,那么你可以省go 存在式而使用联合. 如果N只能是ABC,那么你通常可以只写A | B | C. 在您的用例中,您提到了N可能只是T的属性类型,它们是array. 你可以计算这些:

type ArrayElements<T> = {
    [K in keyof T]: T[K] extends readonly (infer U)[] ? U : never
}[keyof T];

type AEC = ArrayElements<Cargo>;
// type AEC = Model

N可以用这个来代替:

type SomeSubgridColumn<T> = SubgridColumn<T, ArrayElements<T>>

虽然你可能希望它是ArrayElements<T>:distributed over unions:

type SomeSubgridColumn<T> = 
  ArrayElements<T> extends infer N ? N extends any ?
    SubgridColumn<T, N> : never : never;

我不会离题进一步解释那么多,但假设如果N对于给定的T可以是A | B | C,你希望SomeSubgridColumn<T>SubgridColumn<T, A> | SubgridColumn<T, B> | SubgridColumn<T, C>而不是SubgridColumn<T, A | B | C>. 对于给定的用例,这可能足够,也可能不足够.它比any更复杂,但没有编码存在句那么奇怪的反转.

Playground link to code

Typescript相关问答推荐

调用另一个函数的函数的Typescript类型,参数相同

Typescript:将内部函数参数推断为子对象键的类型

对于使用另一个对象键和值类型的对象使用TS Generics

在类型内部使用泛型类型时,不能使用其他字符串索引

泛型和数组:为什么我最终使用了`泛型T []`而不是`泛型T []`?<><>

HttpClient中的文本响应类型选项使用什么编码?''

Typescript泛型类型:按其嵌套属性映射记录列表

如何在TypeScrip中正确键入元素和事件目标?

TS如何不立即将表达式转换为完全不可变?

有没有可能为这样的函数写一个类型?

使用TypeScrip5强制使用方法参数类型的实验方法修饰符

TYPE FOR ARRAY,其中每个泛型元素是单独的键类型对,然后在该数组上循环

在正常函数内部调用异步函数

TS2739将接口类型变量赋值给具体变量

当数组类型被扩展并提供标准数组时,TypeScrip不会抱怨

使用Type脚本更新对象属性

确保财产存在

如何按成员资格排除或 Select 元组?

为什么 Typescript 无法正确推断数组元素的类型?

递归地排除/省略两种类型之间的公共属性