【问题标题】:Structural typing and user-defined type guards结构类型和用户定义的类型保护
【发布时间】:2019-01-03 22:36:10
【问题描述】:
type GraphQLType =
    | GraphQLInt
    | GraphQLList<any>
    | GraphQLNonNull<any>;

interface GraphQLInt {
  int: number
}

interface GraphQLList<T> {
  x: string
}

interface GraphQLNonNull<T> {
  x: string
}

declare function isInt(type: GraphQLType): type is GraphQLInt;
declare function isList(type: GraphQLType): type is GraphQLList<any>;
declare function isNonNull(type: GraphQLType): type is GraphQLNonNull<any>;

function doIt(t: GraphQLType) {
  if (isInt(t)) {
    return t.int
  }

  if (isList(t)) {
    // t: GraphQLList<any> | GraphQLNonNull<any>
    return t.x
  }

  // t: never

  if (isNonNull(t)) {
    return t.x
  }
}

上面的示例在 isNonNull() 块中导致错误,因为它确定 t 的类型为 never。在 isList() 块中,t 具有 GraphQLList 和 GraphQLNonNull 两种类型。这两种类型在结构上是相同的。这是herehere 描述的同一个问题还是实际上是一个错误?

它应该起作用的原因是因为 isList() 是 GraphQLList 而不是 GraphQLNonNull 的类型保护,并且在运行时它会为 List 返回 true,为 NonNull 返回 false,但 typescript 似乎并不代表相同想法。

【问题讨论】:

  • TypeScript 对名义类型(不同名称的类型是不同的类型)的支持很少或不存在。如果你想让 TypeScript 知道两种类型是不同的,最好让它们在结构上有所不同。 GraphQLListGraphQLNonNull 之间没有结构上的区别(它们都没有使用它们的类型参数 T)。如果你想让它工作,给它们一些不同的属性。
  • 相关FAQ entry
  • 它们都没有使用它们的类型参数 T:这只是为了这个例子。我无法更改类型,它们来自 GraphQL.js

标签: typescript structural-typing


【解决方案1】:

类型保护将通过将变量的类型缩小为可分配给受保护类型的任何内容来工作

例如:

function isString(s: any) : s is { o: string } {
    return typeof s.o === 'string'; 
}

let s!: number | { o: 'a', n: number } | { o : 'b', b: boolean};
if(isString(s)) {
    s // { o: 'a', n: number } | { o : 'b', b: boolean}
}

if 中缩小到的联合中的两种类型并不完全属于受保护类型,而是可以分配给它,因此两种类型最终都属于缩小类型,而number 则不是,因为它是不可分配给{ o : string }

在您的示例中应用相同的逻辑,类型在名义上不同的类型并不重要,因为 GraphQLListGraphQLNonNull 都可以相互分配,任何守卫都会选择这两种类型。

正如@jcalz 在 cmets 中指出的那样,您能做的最好的事情就是以某种不会产生太大影响的方式使类型在结构上不兼容。最简单的方法是为每个接口添加一个可选的唯一符号:

type GraphQLType =
    | GraphQLInt
    | GraphQLList<any>
    | GraphQLNonNull<any>;

interface GraphQLInt {
    int: number
}

interface GraphQLList<T> {
    x: string
    readonly __u?: unique symbol;
}

interface GraphQLNonNull<T> {
    x: string
    readonly __u?: unique symbol;
}

declare function isInt(type: GraphQLType): type is GraphQLInt;
declare function isList(type: GraphQLType): type is GraphQLList<any>;
declare function isNonNull(type: GraphQLType): type is GraphQLNonNull<any>;

function doIt(t: GraphQLType) {
    if (isInt(t)) {
        return t.int
    }

    if (isList(t)) {
        // t: GraphQLList<any>
        return t.x
    }

    // t: GraphQLNonNull<any>

    if (isNonNull(t)) {
        return t.x
    }
}

编辑

您注意到类型来自GraphQL,您可以使用模块扩充和接口合并来扩展接口。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-19
    • 2021-10-31
    • 1970-01-01
    • 2020-11-11
    • 2022-01-12
    • 2018-03-26
    • 2016-09-06
    • 1970-01-01
    相关资源
    最近更新 更多