【typescript】断言签名与谓词签名!详解ts中神奇的asserts与is

    科技2026-04-23  2

    前言

    最初发现有这玩意是在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也做不到的事情。

    Processed: 0.009, SQL: 9