我如何断言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个对象听起来是错误的,因为这与我的应用程序逻辑无关。
答案 0 :(得分:1)
如果您有两个运行时对象并将它们进行相等性比较(请注意,在大多数情况下,检查typeof user1 === typeof user2
和user1 === 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
此处,apples
和oranges
在运行时相同,但根据编译器的不同,它们的类型也不同。仅仅因为apples === oranges
并不意味着Apples
是Oranges
。
正如我在评论中提到的那样,到JavaScript运行时,整个TypeScript静态类型系统(包括所有interface
定义)已经erased。运行时为时已晚,无法捕获此类错误。
相反,您可以使编译器出错。让我们定义一个称为MutuallyExtends<T, U>
的通用类型别名,其中T
和U
彼此为constrained。 (严格来说,这是被禁止的循环约束。相反,我们可以做的是向V
输入的内容中引入defaults的新泛型参数T
。如果A extends B
和B extends A
与A
的类型与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_Bad
与UserData
不兼容,甚至给出了有关原因的一些信息。据推测,这应该会破坏您的构建(或至少在构建过程中发出警告),并为您提供在运行任何JavaScript代码之前对其进行处理的机会。
无论如何,希望能给您一些指导。祝你好运!