结构化类型和用户定义的类型保护

时间:2018-07-26 20:37:26

标签: typescript structural-typing

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,但是打字稿似乎并不代表相同想法。

1 个答案:

答案 0 :(得分: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在评论中指出的那样,您可以做的最好的事情就是使类型在某种程度上在结构上不兼容,而不会产生太大的影响。最简单的方法是在每个接口中添加一个可选的唯一符号:

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,您可以使用模块扩充和接口合并来扩展接口。