【问题标题】:Enforce interface for array of objects and also create type from a mapped value为对象数组实施接口,并从映射值创建类型
【发布时间】:2022-08-04 18:13:33
【问题描述】:

我正在使用打字稿来确保队列满足IQueue 接口:

export interface IQueue {
  id: string;
  handler: () => void;
}

const queues:IQueue[] = [
  { id: \'a\', handler: () => { } },
  { id: \'b\' }, // handler is missing, should be an error
];

我还想要一个 QueueId 类型,它是所有 id 的联合:

const queues = [
  { id: \'a\', handler: () => { } },
  { id: \'b\' },
] as const;


export declare type QueueId = (typeof queues[number])[\'id\'];

export const start = (queueId:QueueId) => {
  ...
};

start(\'z\'); // should be a typescript error

但我不能让他们一起工作。 QueueId 类型需要 as const 类型。有几篇文章建议进行 noop 强制转换,但我收到 readonly cannot be assigned to the mutable type... 错误。所以我试着让它可写,但它给出了一个“重叠不足”的错误:

type DeepWriteable<T> = { -readonly [P in keyof T]: DeepWriteable<T[P]> };
(queues as DeepWriteable<typeof queues>) as IQueue[];

有可能两者都做吗?

这是一个完整的例子:

Playground

  • this approach 是否满足您的需求?如果是这样,我可以写一个答案;如果没有,我错过了什么?
  • 哇,是的 - 太棒了。在这里,我认为我擅长打字。我将不得不研究 asQueues 魔法
  • 好的,当我有机会时,我会写一个解释它的答案。
  • 我选择了一种更通用的方法,这种方法与您所做的事情的差异较小。如果你真的想让我写出asQueues() 的工作原理,我可以(但也许不是今天)

标签: typescript


【解决方案1】:

首先,如果您希望编译器为id 属性推断字符串literal types,而不为queues 推断a readonly tuple type,那么您可以将const assertionqueues 初始化程序移动到仅id 属性有问题:

const queues = [
  {
    id: 'x' as const,
    handler: () => { },
  },
  {
    id: 'y' as const,
    handler: () => { },
  },
];

/* const queues: ({
     id: "x";
     handler: () => void;
   } | {
     id: "y";
     handler: () => void;
   })[] */

type QueueId = (typeof queues[number])['id'];
// type QueueId = "x" | "y"

此时,您要检查queues 的类型是否可分配给IQueue[],而实际上实际上annotating 不是IQueue[],因为这会使编译器完全忘记"x""y"

TypeScript 目前没有内置的类型运算符来执行此操作;在microsoft/TypeScript#47920 有一个(暂时)称为satisfies 的功能请求,您可能会在其中写类似

// this is not valid TS4.6-, don't try it:
const queues = ([
  {
    id: 'x' as const,
    handler: () => { },
  },
  {
    id: 'y' as const,
    handler: () => { },
  },
]) satisfies IQueue[];

如果您遗漏了handler 或其他内容,编译器会抱怨。但是没有satisfies 运算符。

幸运的是,您基本上可以编写一个辅助函数(如果您眯着眼睛看它),它的行为类似于 satisfies 运算符。不要写x satisfies T,而是写satisfies&lt;T&gt;()(x)。这是你写它的方式:

const satisfies = <T,>() => <U extends T>(u: U) => u;

额外的() 是因为satisfies 是一个咖喱函数为了允许您手动指定T,同时让编译器推断U。有关更多信息,请参阅Typescript: infer type of generic after optional first generic

反正我们在使用的时候,我们可以看到如果你搞砸了它会报错:

const badQueues = satisfies<IQueue[]>()([
  {
    id: 'x' as const,
    handler: () => { },
  },
  { id: 'y' as const }, // error!
  // ~~~~~~~~~~~~~~~~~ <-- Property 'handler' is missing
]);

当你不搞砸的时候,它不会忘记'x''y'

const queues = satisfies<IQueue[]>()([
  {
    id: 'x' as const,
    handler: () => { },
  },
  {
    id: 'y' as const,
    handler: () => { },
  },
]);

/* const queues: ({
     id: "x";
     handler: () => void;
   } | {
     id: "y";
     handler: () => void;
   })[]
*/

type QueueId = (typeof queues[number])['id'];
// type QueueId = "x" | "y"

看起来不错!

Playground link to code

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-01-05
    • 1970-01-01
    • 2020-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多