如何在不初始化变量的情况下声明接口相等性?

时间:2020-06-11 00:57:20

标签: typescript interface equality

我如何断言2个接口是相同的(鸭式输入)而无需初始化任何值?

我使用graphql代码生成工具自动为graphql查询生成类型。 当我从不同的地方查询相同的数据时,会生成相同的接口。 我想通过在所有文件中导入相同的接口来巩固我的输入,同时确保确实接口的不同声明是相同的。

示例:

// src/components/__generated__/MyData.ts
export interface MyData_me {
  __typename: "User";
  id: string;
  name: string | null;
  avatarUrl: string | null;
  email: string | null;
}
// src/models/__generated__/UserData.ts
export interface UserData {
  __typename: "User";
  id: string;
  name: string | null;
  avatarUrl: string | null;
  email: string | null;
}
// src/models/User.ts
import { gql } from "@apollo/client";

import {MyData, MyData_me} from "../components/__generated__/MyData";
import {UserData} from "./__generated__/UserData";

export const UserDataFragment = gql`
  fragment UserData on User {
    id
    name
    avatarUrl
    email
  }
`

if (UserData !== MyData_me) { // [tsserver 2693] [E] 'UserData' only refers to a type, but is being used as a value here
  throw "We have a problem, interfaces TUser and MyData_me aren't identical!"
}
export type TUser = UserData;
export type TMe = MyData; 

这里有干净的解决方案吗?为了测试if (typeof user1 !== typeof user2)被迫使用伪数据初始化2个对象听起来是错误的,因为这与我的应用程序逻辑无关。

1 个答案:

答案 0 :(得分:1)

如果您有两个运行时对象并将它们进行相等性比较(请注意,在大多数情况下,检查typeof user1 === typeof user2user1 === user2并不等同于检查相等性),它不会告诉您TypeScript是否认为它们是同一类型。例如:

const fruit = { apples: 5, oranges: 5 };

interface Apples {
    apples: number;
}
const apples: Apples = fruit; // okay

interface Oranges {
    oranges: number;
}
const oranges: Oranges = fruit; // okay

console.log(JSON.stringify(apples) === JSON.stringify(oranges)); // true
// but Apples and Oranges are not the same type

此处,applesoranges在运行时相同,但根据编译器的不同,它们的类型也不同。仅仅因为apples === oranges并不意味着ApplesOranges


正如我在评论中提到的那样,到JavaScript运行时,整个TypeScript静态类型系统(包括所有interface定义)已经erased。运行时为时已晚,无法捕获此类错误。

相反,您可以使编译器出错。让我们定义一个称为MutuallyExtends<T, U>的通用类型别名,其中TU彼此为constrained。 (严格来说,这是被禁止的循环约束。相反,我们可以做的是向V输入的内容中引入defaults的新泛型参数T。如果A extends BB extends AA的类型与B相同,则并非完全正确,但这非常接近。有stricter checks available,但是互惠通常对我来说已经足够了。无论如何,这里是:

type MutuallyExtends<T extends U, U extends V, V = T> = any;

MutuallyExtends<T, U>评估的实际值仅仅是any,而不是我们真正关心的。关键是除非编译器认为MutuallyExtends<X, Y>X是可分配的,否则您不能编写Y

我们要做的就是将MutuallyExtends<MyData_me, UserData>写在某个类型位置的某处,看看是否有错误。就像这样说:

0 as MutuallyExtends<MyData_me, UserData>; // no error here

在仅变为0;的运行时,对0的无用评估。但是重要的是,编译器中没有类型错误。


将其与使用

之类的错误类型时发生的情况进行对比
interface MyData_Bad {
    _typename: "User"; // wrong number of underscores
    id: string;
    name: string | null;
    avatarUrl: string | null;
    email: string | null;
}

0 as MutuallyExtends<MyData_Bad, UserData>; // error!
// ----------------> ~~~~~~~~~~
// Type 'MyData_Bad' does not satisfy the constraint 'UserData'.
//   Property '__typename' is missing in type 'MyData_Bad' but 
//   required in type 'UserData'.

现在出现一个错误,告诉您MyData_BadUserData不兼容,甚至给出了有关原因的一些信息。据推测,这应该会破坏您的构建(或至少在构建过程中发出警告),并为您提供在运行任何JavaScript代码之前对其进行处理的机会。


无论如何,希望能给您一些指导。祝你好运!

Playground link to code

相关问题