引言
- 语法
T extends U ? X : Y
。一般语意为:如果 checkType 可赋值给 extendType ,则结果为真分支类型 tureType ,否则结果为假分支类型 falseType - 探讨
never
、unknown
、any
等特殊类型的原因及结果 - 这系列文章目前打算走推导式风格(应该?),前面定义、概念挺多的,静下来心来慢慢看呗。
类型概念阐述
部分背景如上篇文章所述:【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,也不提供属性提示。
null
和undefined
这两个类型受 “strictNullChecks” 选项的影响,开启和关闭该选项有不同的行为:
不启用 strictNullChecks
null
和undefined
被TS视为可以赋值给任意类型的特殊值,所有非空集合都会包含null
和undefined
。比如
- 字面量字符串类型
"1"
,此时它对应的集合元素有3个:"1"
、null
、undefined
。 - 类型
null
包含两个元素:null
、undefined
- 类型
undefined
包含两个元素:null
、undefined
启用 strictNullChecks
null
和undefined
不再被视为特殊值。
- 字面量字符串类型
"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"
any
与unknown
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
any
与unknown
的组合会相当有趣,判断逻辑顺序和优先度如下:
- 当 extendsType
any
或unknown
时,始终返回 trueType - 当 checkType 为
any
时,始终返回联合类型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,里面有个图很秀)
最后

???? 各位看官一定懂 ?????
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!