如何在不初始化变量的情况下断言接口相等?
How to assert interface equality without initializing variables?
如何在不初始化任何值的情况下断言 2 个接口相同(duck-typing)?
我使用 graphql codegen 工具自动为 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;
这里有干净的解决方案吗?被迫用假数据初始化 2 个对象以进行测试 if (typeof user1 !== typeof user2)
听起来不对,因为它与我的应用程序逻辑无关。
如果你有两个运行时对象并比较它们是否相等(请注意,在大多数情况下检查 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 to each other. (Strictly speaking this would be a prohibited circular constraint. Instead what we can do is introduce a new generic type parameter V
that defaults to whatever is entered for T
). It's not exactly true that if A extends B
and if B extends A
that A
is the same type as B
, but it's pretty close. There are 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 代码运行之前做一些事情。
无论如何,希望能给你一些指导。祝你好运!
如何在不初始化任何值的情况下断言 2 个接口相同(duck-typing)?
我使用 graphql codegen 工具自动为 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;
这里有干净的解决方案吗?被迫用假数据初始化 2 个对象以进行测试 if (typeof user1 !== typeof user2)
听起来不对,因为它与我的应用程序逻辑无关。
如果你有两个运行时对象并比较它们是否相等(请注意,在大多数情况下检查 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 to each other. (Strictly speaking this would be a prohibited circular constraint. Instead what we can do is introduce a new generic type parameter V
that defaults to whatever is entered for T
). It's not exactly true that if A extends B
and if B extends A
that A
is the same type as B
, but it's pretty close. There are 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 代码运行之前做一些事情。
无论如何,希望能给你一些指导。祝你好运!