最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【Typescript】进击的基础(二)条件类型

    正文概述 掘金(teriri)   2020-12-13   429

    引言

    1. 语法 T extends U ? X : Y。一般语意为:如果 checkType 可赋值给 extendType ,则结果为真分支类型 tureType ,否则结果为假分支类型 falseType
    2. 探讨neverunknownany 等特殊类型的原因及结果
    3. 这系列文章目前打算走推导式风格(应该?),前面定义、概念挺多的,静下来心来慢慢看呗。

    类型概念阐述

    部分背景如上篇文章所述:【Typescript】进击的基础(一)交叉类型和联合类型-集合论角度理解

    这里也会套用部分数学概念,结合实际情况和资料推测typescript的行为。

    never

    如果使用数学的 集合 概念来描述typescript类型,那么never对应的就是“空集”。套用空集的运算概念,可以得出以下结果:

    type A = never | string; // type A = string
    type B = never & string; // type A = never
    const a: string = '' as never;
    const b: never = '' as never;
    const c: never = '' as any; // error
    

    其中比较有意思的是最后一点:空集的子集只有空集本身,所以never类型只接受never类型的赋值;而any在TS中是作为类型后门的存在,可赋值给任何类型。这里的问题就出来了,要遵守哪一条设计呢?为什么最后会采用any不可赋值给never这个设计?

    个人愚见,实际上never出现的情况基本都是明确告知用户不要使用该类型的变量,或者此处出现错误。所以从安全角度出发,不允许any赋值给never

    unknown

    对应“全集”概念:所有集合都是全集的子集,所以任意类型的变量都可以赋值给unknown类型。

    any的区别就在于unknown仍然是服从整体类型设计的,并不是作为类型后门的存在。比如当你希望一个参数可以接受任意类型时,应当声明为unknown而不是any

    关于unknown类型没有任何“属性的类型提示”:同前文思想,unknown类型虽然代表着一个变量可能有任意属性,但这同时意味着一个变量可能没有某些属性。TS无法确保属性是否存在,所以该类型变量的任意属性读写都视作error,也不提供属性提示。

    nullundefined

    这两个类型受 “strictNullChecks” 选项的影响,开启和关闭该选项有不同的行为:

    不启用 strictNullChecks

    nullundefined被TS视为可以赋值给任意类型的特殊值,所有非空集合都会包含nullundefined。比如

    • 字面量字符串类型"1",此时它对应的集合元素有3个: "1"nullundefined
    • 类型null包含两个元素:nullundefined
    • 类型undefined包含两个元素:nullundefined

    启用 strictNullChecks

    nullundefined不再被视为特殊值。

    • 字面量字符串类型"1",此时它对应的集合元素只有1个: "1"
    • 类型null只包含1个元素:null
    • 类型undefined只包含1个元素:undefined

    条件类型运算

    基于前面的描述,下面的示例套入相应集合概念,大部分特殊值运算都是可以推导得到的(启用strictNullChecks选项):

    type A = null extends unknown ? true : false; // true
    type B = 1 extends unknown ? true : false; // true
    type C = unknown extends 1 ? true : false; // false
    

    Distributive conditional types

    总结:如果 checkType联合类型 且是 泛型参数,那么结果等价于各个联合类型各自运算的结果并集,是Typescript有意设计而为之的特性。

    即如果 type T = A | B | C ,那么 T extends U ? X : Y 等价于 (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)。注意要求的位置和生效条件:

    type T = 1 | 2;
    
    type Result1 = T extends 1 ? true : false; // false,没有触发Distributive特性
    
    type F<E> = E extends 1 ? true : false;
    type Result2 = F<T>; // boolean, 即 true | false,触发Distributive特性
    
    type FF<E> = 1 | 2 extends E ? true : false;
    type Result3 = FF<1>; // false,没有触发Distributive特性
    

    避免真假分支类型同时成立

    如果不需要该Distributive特性,只想根据条件的真或假来得到结果,那么利用 元组类型 避开 Distributive 特性即可:

    type T = 1 | 2;
    type F<E> = [E] extends [1] ? true : false;
    type Result = F<T>; // false
    

    过滤联合类型

    结合空集never的特性,可以提供各类高级玩法,如“过滤”:

    type F<T> = T extends string ? T : never
    type Result = F<"1" | 2>
                = ("1" extends string ? "1" : never) | (2 extends string ? 2 : never)
                = "1" | never
                = "1"
    

    anyunknown

    type D = unknown extends any ? true : false; // true
    type E = any extends unknown ? true : false; // true
    type F = unknown extends never ? true : false; // false
    type G = any extends never ? true : false; // boolean ,即 true | false
    

    anyunknown的组合会相当有趣,判断逻辑顺序和优先度如下:

    1. extendsType anyunknown时,始终返回 trueType
    2. checkTypeany时,始终返回联合类型 trueType | falseType

    这段逻辑是直接写入到判断当中的,具体可点击展开:

    getConditionalType
    function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined): Type {
      let result;
      let extraTypes: Type[] | undefined;
      // We loop here for an immediately nested conditional type in the false position, effectively treating
      // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for
      // purposes of resolution. This means such types aren't subject to the instatiation depth limiter.
      while (true) {
          const checkType = instantiateType(root.checkType, mapper);
          const checkTypeInstantiable = isGenericObjectType(checkType) || isGenericIndexType(checkType);
          const extendsType = instantiateType(root.extendsType, mapper);
          if (checkType === wildcardType || extendsType === wildcardType) {
              return wildcardType;
          }
          let combinedMapper: TypeMapper | undefined;
          if (root.inferTypeParameters) {
              const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None);
              // We skip inference of the possible `infer` types unles the `extendsType` _is_ an infer type
              // if it was, it's trivial to say that extendsType = checkType, however such a pattern is used to
              // "reset" the type being build up during constraint calculation and avoid making an apparently "infinite" constraint
              // so in those cases we refain from performing inference and retain the uninfered type parameter
              if (!checkTypeInstantiable || !some(root.inferTypeParameters, t => t === extendsType)) {
                  // We don't want inferences from constraints as they may cause us to eagerly resolve the
                  // conditional type instead of deferring resolution. Also, we always want strict function
                  // types rules (i.e. proper contravariance) for inferences.
                  inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
              }
              combinedMapper = mergeTypeMappers(mapper, context.mapper);
          }
          // Instantiate the extends type including inferences for 'infer T' type parameters
          const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType;
          // We attempt to resolve the conditional type only when the check and extends types are non-generic
          if (!checkTypeInstantiable && !isGenericObjectType(inferredExtendsType) && !isGenericIndexType(inferredExtendsType)) {
              // Return falseType for a definitely false extends check. We check an instantiations of the two
              // types with type parameters mapped to the wildcard type, the most permissive instantiations
              // possible (the wildcard type is assignable to and from all types). If those are not related,
              // then no instantiations will be and we can just return the false branch type.
              if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && (checkType.flags & TypeFlags.Any || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) {
                  // Return union of trueType and falseType for 'any' since it matches anything
                  if (checkType.flags & TypeFlags.Any) {
                      (extraTypes || (extraTypes = [])).push(instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper));
                  }
                  // If falseType is an immediately nested conditional type that isn't distributive or has an
                  // identical checkType, switch to that type and loop.
                  const falseType = getTypeFromTypeNode(root.node.falseType);
                  if (falseType.flags & TypeFlags.Conditional) {
                      const newRoot = (<ConditionalType>falseType).root;
                      if (newRoot.node.parent === root.node && (!newRoot.isDistributive || newRoot.checkType === root.checkType)) {
                          root = newRoot;
                          continue;
                      }
                  }
                  result = instantiateType(falseType, mapper);
                  break;
              }
              // Return trueType for a definitely true extends check. We check instantiations of the two
              // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter
              // that has no constraint. This ensures that, for example, the type
              //   type Foo<T extends { x: any }> = T extends { x: string } ? string : number
              // doesn't immediately resolve to 'string' instead of being deferred.
              if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) {
                  result = instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper);
                  break;
              }
          }
          // Return a deferred type for a check that is neither definitely true nor definitely false
          const erasedCheckType = getActualTypeVariable(checkType);
          result = <ConditionalType>createType(TypeFlags.Conditional);
          result.root = root;
          result.checkType = erasedCheckType;
          result.extendsType = extendsType;
          result.mapper = mapper;
          result.combinedMapper = combinedMapper;
          result.aliasSymbol = root.aliasSymbol;
          result.aliasTypeArguments = instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217
          break;
      }
      return extraTypes ? getUnionType(append(extraTypes, result)) : result;
    }
    

    第二点具体设计的原因不清楚,目前只在代码中找到一行注释: Return union of trueType and falseType for 'any' since it matches anything

    不过看到相关issues有提到的一个有趣的说法:在该情形下,把any视为任意类型的联合类型(即any = 0 | 1 | 2 ....),同时视作泛型通配类型,那么搭配 Distributive 特性后结果就会是 trueType | falseType

    infer

    TS类型体操的必备技能。推荐:深入理解 TypeScript--infer

    目前抓不到什么值得再写一遍的闪亮点,此略了。

    参考资料

    Conditional Types

    Add 'never' type(never 的PR)

    Conditional type T extends U ? X : Y is either resolved to X or Y, should not be both (X|Y) (issue)

    Question / Suggestion: Behaviour of unknown in distributive conditional type (issue,里面有个图很秀)

    最后

    【Typescript】进击的基础(二)条件类型

    ???? 各位看官一定懂 ?????


    下载网 » 【Typescript】进击的基础(二)条件类型

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元