gpt4 book ai didi

强制使用给定类型之一的 TypeScript 类型

转载 作者:行者123 更新时间:2023-12-04 07:51:41 25 4
gpt4 key购买 nike

在将其标记为重复之前,它不是,至少不是前 10 个以上的搜索结果中的任何一个。这种情况增加了我无法弄清楚的复杂程度。
tl;博士 用于专门指定 n 的一个属性必须存在,或者属性必须恰好存在于 n 个点之一的方法,在嵌套属性上使用时不起作用。 TS Playground
我正在做一些周六的过度设计——就像你做的那样。我的项目使用 JSON HAL documents (HAL 文档基本上描述了某种实体,并具有 _links 属性,定义了可用的行为),但是,后端(我没有写)并不总是以相同的形状发送文档。我有一个 normalizeRepresentation()接收服务器发送给我的内容并将其变形为它应该是什么样子的函数。
为了生成最少的代码,我删除了有关 _links 的内容。 .
标准 HAL 之间的差异:

  • 实体数据正好位于以下之一:
  • 根文档本身
  • document属性(property)
  • data属性(property)
  • item属性(property)
  • item.data属性(property)

  • 有一个可选的 contentType放置在根文档或实体数据中的属性,但不能同时放置
  • normalizeRepresentation()最初有签名:
    function normalizeRepresentation(representation: Record<PropertyKey, any>): IHalDocument;
    我想强输入 IHalRepresentation<T>为了强类型 normalizeRepresentation() :
    function normalizeRepresentation<T = any>(representation: IHalRepresentation<T>): IHalDocument<T>;
    所以,如果 T{ id: number } ,正确表示的一些例子是:
    const valid: IHalRepresentation<{ id: number }>[] = [
    { id: 1 },
    { id: 1, contentType: 'foo' },
    { data: { id: 1 }, contentType: 'foo' },
    { document: { id: 1, contentType: 'foo' } },
    { item: { data: { id: 1, contentType: 'foo' } } },
    { item: { data: { id: 1 } }, contentType: 'foo' },
    ];
    一些无效文件包括:
    const invalid: IHalRepresentation<{ id: number }>[] = [
    // data is both on document and in data prop
    { id: 1, data: { id: 1 } },

    // both data and item are specified
    { data: { id: 1 }, item: { contentType: 'foo' } },

    // contentType is on item when item.data exists
    { item: { data: { id: 1 }, contentType: 'foo' } },

    // contentType is included twice
    { item: { data: { id: 1, contentType: 'foo' } }, contentType: 'foo' },
    ];
    这些案例中的大部分都在我工作中。唯一的问题发生在 { item: { data: T } }案件。
    这是我的主要工作代码:
    /** 
    * Adds an optional `contentType` property on either `T[K]` or as a sibling of `K`.
    */
    type WithContentType<T, K extends keyof T> =
    | { [_K in K]: T[_K] & { contentType?: string } } & { contentType?: never }
    | { [_K in K]: T[_K] & { contentType?: never } } & { contentType?: string };

    /**
    * The three root properties that may contain our data.
    */
    type ItemProperties = 'data' | 'item' | 'document';

    /**
    * @param T The data of the HAL document used for describing the entity.
    * @param P The property we want to put `T` on. If `null`, places it at the root.
    * @param CTProp The property of `T` in which to append `contentType`. If `null`, places it on `T` itself.
    */
    type Content<T, P extends ItemProperties | null, CTProp extends null | keyof T = null> =
    P extends null
    ? T & { contentType?: string; data?: never; item?: never; document?: never }
    : CTProp extends null
    ? WithContentType<{ [K in P & ItemProperties]: T }, P & ItemProperties> & { [K in Exclude<ItemProperties | keyof T, P>]?: never }
    : { [K in P & ItemProperties]: WithContentType<T, CTProp & keyof T> } & { [K in Exclude<ItemProperties | keyof T, P>]?: never };

    type IHalRepresentation<T extends Record<string, any>> =
    // case when all the data is stored at the root of the document
    | Content<T, null>
    // case when data is stored on the `data` property
    | Content<T, 'data'>
    // case when data is stored on the `item` property
    | Content<T, 'item'>
    // case when data is stored on the `item.data` property
    | Content<{ data: T }, 'item', 'data'>
    // case when data is stored on the `document` property
    | Content<T, 'document'>;
    以下是失败的三种情况:
    interface Doc { id: number }

    // @ts-expect-error
    const failureItemDataWithTwoContentTypes : IHalRepresentation<Doc> = {
    item: {
    data: {
    id: 1,
    contentType: 'bar',
    }
    },
    contentType: 'foo',
    };

    // @ts-expect-error
    const failureItemDataWithContentTypeOnItem : IHalRepresentation<Doc> = {
    item: {
    data: { id: 1 },
    contentType: 'bar',
    },
    };

    // @ts-expect-error
    const failureItemandItemData: IHalRepresentation<Doc> = {
    item: {
    id: 1,
    data: { id: 2 },
    },
    };
    在这些情况下,编译器都不会检测到它们是无效的。我怀疑解决一个案例会解决所有问题。似乎认为如果 item.data指定, item可以 T{ data: T } .不过,我终其一生都看不到我的错误在哪里。
    我注意到的另一个奇怪的地方可能会或可能不会指向我们正确的方向是在 VSCode 中,它知道在 P extends null ? ... : 之后 P独家扩展 ItemProperties还有那个 CTProp必须扩展 keyof JSONSansLinks<T>> ,而 TS Playground 没有,我不得不手动指定它们。
    我的团队正在使用 TS v3.9.7,但我在 v4.2.3 中看到了同样的问题。
    TS Playground

    最佳答案

    这可能是我做过的最棘手的类型争论,这里是(谨慎行事):

    type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };

    type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : (T | U);

    type XORs<T extends unknown[]> = T extends [infer P1, infer P2] ? XOR<P1, P2> : T extends [infer Only, ...infer Rest] ? XOR<Only, XORs<Rest>> : never;

    type Never<T> = T extends object ? { [P in keyof T]?: never } : never;

    interface IContentType {
    contentType?: string;
    }

    interface IHalBaseLinks {
    links: string[];
    }

    interface IHalBaseULinks {
    _links: string[];
    }

    type IHalDocBase = XOR<IHalBaseLinks, IHalBaseULinks>;
    type IHalIsData<T> = IHalDocBase & T;
    type IHalHasData<T> = IHalIsData<{ data: T }>
    type IHalHasDocument<T> = IHalIsData<{ document: T }>;
    type IHalHasItem<T> = IHalIsData<{ item: XOR<T, {data: T}> }>;

    type IHalWithData<T> = XORs<[IHalHasData<T>, IHalHasDocument<T>, IHalHasItem<T>]>;
    type IHalRepresentation<T extends object> = XORs<[IContentType & IHalIsData<T>, IHalWithData<T & IContentType>, IContentType & IHalWithData<T & Never<IContentType>>]>;
    这样做学到了很多。具体来说, |&并不像一开始看起来那么直观,这就是为什么我必须在 Typescript 中专门寻找互斥类型(目前不存在),从而登陆 XOR构造(从 ts-xor 复制)用于实际完成这项工作。
    我仍然不完全理解为什么/如何 XOR事情有效,但它是使这个怪物复活的 secret 成分。
    最难弄清楚的是如何确保 contentType要么在根中,要么在对象内部。
    Playground 上查看(完成测试)

    关于强制使用给定类型之一的 TypeScript 类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66936722/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com