前言
- 最初发现有这玩意是在styledcomponents的声明中,很神奇的写了个is。
- 后来翻阅官方文档后发现,除了is是谓词签名外,还有assert断言签名。
官方文档
- 对于这种东西讲解,最好的方法就是先甩个官方文档。
- 文档:https://www.typescriptlang.org/docs/handbook/release-notes/overview.html
- 看一下官方给的例子:
function yell(str) {
assert(typeof str === "string");
return str.toUpperCase();
}
- 我们需要断言str是string ,如果str不是string,那么我们没法用大写的方法。
- 比如我们要写这样一个函数:
function yell(str:any) {
if (typeof str !== "string") {
throw new TypeError("str should have been a string.");
}
return str.toUpperCase();
}
- 正常来说,我们使用这种方法,ts会自动检测到后面走的str是个string,从而判断为string。
- 如果我们想动态的判断条件呢?比如这么写:
function yell(str:any) {
assert(typeof str === "string");
return str.toUpperCase();
}
function assert(condition: any) {
if (!condition) {
throw new Error('错误');
}
}
- 由于不是一个函数里进行throw的,并且是进行判断,虽然ts没有报错,但是ts定的str是any而不是string。
- 此时,就需要一种断言参数来进行断言:
function yell(str:any) {
assert(typeof str === "string");
return str.toUpperCase();
}
function assert(condition: any): asserts condition {
if (!condition) {
throw new Error('错误');
}
}
- 对参数进行断言签名,此时str会被ts认定为string,而不是上面那个any。
- 当然,在另一个函数中写throw可能让你觉得是throw帮助了ts。其实帮助ts的是assert,ts不会去检测另一个函数中的判断语句。
- 比如这么写:
function yell(str:any) {
console.log(str)//此时仍是any
assert(typeof str === "string");
return str.toUpperCase();
}
function assert(condition: any): asserts condition {
return condition
}
- 我的assert函数并没有throw 别的类型,直接返回,但是由于asserts存在,下方的str依然变为string。
- 除了asserts断言签名,还有种谓词签名,就是is了。
- is 和asserts有点像,但不一样。还是刚才例子,我们将asserts换成is:
function yell(str:any) {
console.log(str)//此时仍是any
assert(typeof str === "string");
return str.toUpperCase();
}
function assert(condition: any):condition is string {
return condition
}
- 会发现str仍然会被判断为any,而不是string。
- 这是当然的,因为is不是asserts。
- is是谓词签名,所谓谓词签名,就是在另一个函数里强转参数,让使用其函数的函数可以正确判断类型。
- 还是刚才例子,我们利用is来让str变成判断为string。
function yell(str: any) {
console.log(str); //此时仍是any
if (assert(str)) {
return str.toUpperCase();//这里是string
}
return str.toUpperCase();//这里是any
}
function assert(condition: any): condition is string {
return condition;
}
- 可以看见,谓词签名其实就是断言签名的另一种表现形式,当我们断定谓词签名是string,另一个函数是的分支下则被检测为string。如果去了is,则判断仍是any。
- 从中可以发现,ts不会检测另一个函数的判断语句,如果需要,分发判断,就要在另一个函数里做好参数签名,不管是谓词签名还是断言签名都可以。
实战使用
- 上面已经理解了asserts和is的用法,但是如果实战如何使用?
- 对于asserts,其实上面的例子已经可以实战中使用了,动态断言类型而不用管断言函数内部使用。
- 但是对于is,就没办法动态断言(当然结合泛型动态断言是另外用法,暂不讨论)。我们需要结合is的特点来分析。
- is的特点就是解决了函数在另一个函数中调用的参数判断,但是自己的参数判断就是正常的。
- 基于这个特点,如果有个函数提供给别人使用为a类型,自己使用是b类型,这样就可以做出这种神奇的函数。
- 那么什么样的函数会有上面一条特性?
- 这种函数其实很多,几乎80%的复杂函数都会有这种特性!但是99%的人没这么写函数。
- 我举个例子,首先写个接收各种数字的函数,如果数字等于1,2,3,4,5,6中一个则返回true。
function pipsAreValid(pips: number) {
return (
pips === 1 ||
pips === 2 ||
pips === 3 ||
pips === 4 ||
pips === 5 ||
pips === 6
);
}
- 可以把这个类比成写个函数,然后经过各种逻辑,生成个判断。比如验证规则什么的过滤函数。
- 当我把这个函数交给另一个函数使用时,我希望另一个函数经过我这个函数的判断,获得正确的类型推断:
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
function pipsAreValid(pips: number):pips is Dice {
return (
pips === 1 ||
pips === 2 ||
pips === 3 ||
pips === 4 ||
pips === 5 ||
pips === 6
);
}
function evalThrow(count: number) {
if (pipsAreValid(count)) {
console.log(count) //如果不加参数断言,你很可能要在这里断言
}
}
- 这个参数断言有个特点,推断的再次判断会导致参数断言失效:
function evalThrow(count: number) {
if (pipsAreValid(count)===true) {
console.log(count) //number类型
}
}
- 其实这个跟参数断言不关心返回值是一个道理,拿true与它相比,那么就说明计算的是返回值,而不是拿参数进行计算。
- 但是这样是没问题的:
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
function pipsAreValid(pips: number):pips is Dice {
return (
pips === 1 ||
pips === 2 ||
pips === 3 ||
pips === 4 ||
pips === 5 ||
pips === 6
);
}
function demo2(pips:number):pips is 8 | 9{
return(
pips === 8|| pips ===9
)
}
function evalThrow(count: number) {
if (pipsAreValid(count)||demo2(count)) {
console.log(count) //1-9
}
}
- 事实上虽然参数以冒号加断言语句覆盖了原来写返回值的地方,但是返回值仍然隐藏在签名中,仍会奏效。
- 比如你不能这么写:
function evalThrow(count: number) {
switch(count){
case pipsAreValid(count)://类型“boolean”不可与类型“number”进行比较
return ''
}
}
- 但是提供签名却是这样:
function pipsAreValid(pips: number): pips is Dice
- 我还探究了一番,如果我写个声明文件:
xxx.d.ts
declare function newfn(pips: number): pips is Dice;
- 此时我进行调用:
function evalThrow(count: number) {
switch(count){
case newfn(count)://类型“boolean”不可与类型“number”进行比较
return ''
}
}
-
也就是说,写了谓词签名的函数返回值固定为boolean。
-
同理,断言签名asserts固定返回值类型为void。
-
这样,一下子签名方面就明朗起来,使用带谓词签名或者断言签名,可以让我们更加优雅的替代as的强转,甚至做一些as也做不到的事情。