有类型任务的:

type Task = {
  id: string;
  prop1: string;
  prop2: number;
}

我正在try 正确地键入一个参数,以便表达对任务的更新:

  • ID-必填-查找合适的任务
  • 任意数量的props -可选-任务props 的更新

我想出了两个替代方案:

type TaskUpdate1 = Pick<Task, 'id'> & Partial<Omit<Task, 'id'>>;
type TaskUpdate2 = Pick<Task, 'id'> & Partial<Task>;

在我看来,它们是相同的,但两个类型断言库声称它们是不同的:

import { Expect, Equal } from 'type-testing';
import { assert, _ } from "spec.ts";

type Task = {
  id: string;
  prop1: string;
  prop2: number;
}

type TaskUpdate1 = Pick<Task, 'id'> & Partial<Omit<Task, 'id'>>;
type TaskUpdate2 = Pick<Task, 'id'> & Partial<Task>;

type test_0 = Expect<Equal<TaskUpdate1, TaskUpdate2>>;

var x = {} as TaskUpdate1;
var y = {} as TaskUpdate2;

assert(x, y);

这些类型有什么不同(如果有,有什么不同?换句话说:是否存在属于一个而不属于另一个的值?),或者是类型测试代码中的缺陷?

TS Playground

Update

这个问题显然是关于类型测试和规范的知识 我内联了这些库中的类型,以使问题更加独立:

/* type-testing: https://github.com/MichiganTypeScript/type-testing/ */
export type Equal<A, B> =
  (<T>() => T extends A ? 1 : 2) extends
  (<T>() => T extends B ? 1 : 2)
  ? true
  : false;

export type Expect<T extends true> = Equal<T, true>;

/* spec.ts: https://github.com/aleclarson/spec.ts */
// Give "any" its own class
export class Any {
  private _: true = true;
}

type TestExact<Left, Right> =
  (<U>() => U extends Left ? 1 : 0) extends (<U>() => U extends Right ? 1 : 0) ? Any : never;

type IsAny<T> = Any extends T ? ([T] extends [Any] ? 1 : 0) : 0;

export type Test<Left, Right> = IsAny<Left> extends 1
  ? IsAny<Right> extends 1
    ? 1
    : "❌ Left type is 'any' but right type is not"
  : IsAny<Right> extends 1
  ? "❌ Right type is 'any' but left type is not"
  : [Left] extends [Right]
  ? [Right] extends [Left]
    ? Any extends TestExact<Left, Right>
      ? 1
      : "❌ Unexpected or missing 'readonly' property"
    : "❌ Right type is not assignable to left type"
  : "❌ Left type is not assignable to right type";

type Assert<T, U> = U extends 1
  ? T // No error.
  : IsAny<T> extends 1
  ? never // Ensure "any" is refused.
  : U; // Return the error message.

export const assert: <Left, Right>(
  left: Assert<Left, Test<Left, Right>>,
  right: Assert<Right, Test<Left, Right>>
) => Right = () => ({}) as any;

/* My code */

type Task = {
  id: string;
  prop1: string;
  prop2: number;
}

type TaskUpdate1 = Pick<Task, 'id'> & Partial<Omit<Task, 'id'>>;
type TaskUpdate2 = Pick<Task, 'id'> & Partial<Task>;

type test_0 = Expect<Equal<TaskUpdate1, TaskUpdate2>>;

var x = {} as TaskUpdate1;
var y = {} as TaskUpdate2;

assert(x, y);

推荐答案

这两种类型是structually个等价的,这意味着在大多数实际目的中,它们的行为应该是相同的.

你可以通过写一个标识mapped type来遍历每个属性并给出其结果类型来证明 struct 等价性(类似的 idea 见How can I see the full expanded contract of a Typescript type?):

type Id<T> = { [K in keyof T]: T[K] }

type TaskUpdate1 = Pick<Task, 'id'> & Partial<Omit<Task, 'id'>>;
type TU1 = Id<TaskUpdate1>;
/* type TU1 = {
    id: string;
    prop1?: string | undefined;
    prop2?: number | undefined;
}*/

type TaskUpdate2 = Pick<Task, 'id'> & Partial<Task>;
type TU2 = Id<TaskUpdate2>;
/* type TU2 = {
    id: string;
    prop1?: string | undefined;
    prop2?: number | undefined;
}*/

因此,尽管TaskUpdate1TaskUpdate2的表示方式不同,但它们产生相同的TU1TU2类型.


至于为什么Equalassert看起来是不同的,看起来这些类型相等操作符并不是为了判断structural是否等价.相反,它们更接近于判断类型是否实际上由类型判断器以等效的方式表示. 这就是How to test if two types are exactly the same和它的答案. 我不确定使用类型级相等运算符的人如何希望intersections这样的行为,以及这是否会被认为是一个错误,一个设计限制,或者你正在使用的库的预期功能. 但无论如何,我会说它们不符合你的需要,你不应该在这里使用它们.

一般来说,我建议只使用相互可分配性(或compatibility)作为平等的度量标准,例如

declare let tu1: TaskUpdate1;
declare let tu2: TaskUpdate1;
tu1 = tu2; // okay
tu2 = tu1; // okay

直到您发现一个特定的用例,在此用例中这是不够的.

Playground link to code

Typescript相关问答推荐

Angular 垫页分类和垫排序不起作用

接口DOMRouter选项未输出

如何将http上下文附加到angular中翻译模块发送的请求

在Angular中,如何在文件上传后清除文件上传文本框

如何使类型只能有来自另一个类型的键,但键可以有一个新值,新键应该抛出一个错误

为什么typescript要把这个空合并转换成三元?

如何使用泛型类型访问对象

在NextJS中获得Spotify Oauth,但不工作'

根据另一个属性的值来推断属性的类型

如何在TypeScrip中从字符串联合类型中省略%1值

迭代通过具有泛型值的映射类型记录

TypeScrip:从对象中提取和处理特定类型的键

从类型脚本中元组的尾部获取第n个类型

在打字应用程序中使用Zod和Reaction-Hook-Forms时显示中断打字时出错

在类型脚本中创建显式不安全的私有类成员访问函数

更漂亮的格式数组<;T>;到T[]

Karma Angular Unit测试如何创建未解析的promise并在单个测试中解决它,以判断当时的代码是否只在解决后运行

对于通过 Axios 获取的数据数组,属性map在类型never上不存在

广义泛型类型不加载抽象类型上下文

如何在Typescript 中定义具有相同类型的两个泛型类型的两个字段?