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

前言

  • 最初发现有这玩意是在styledcomponents的声明中,很神奇的写了个is。
  • 后来翻阅官方文档后发现,除了is是谓词签名外,还有assert断言签名。

官方文档

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也做不到的事情。

相关推荐
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:马嘣嘣 返回首页