什么时候对ts变量进行类型约束?

175 阅读5分钟

版本一

一、对于简单类型

对于简单类型变量直接定义就行,不需要添加类型,ts内部自带类型推断机制;

ts 中,我们可以对变量的类型进行约束,比如下面这样:

image.png

当我们把 a 声明为 number 类型的变量,就不可以再为它赋其他类型的值;

但实际上,即使我们不显式指定它的类型,ts也可以自动推断出正确的类型,如下:

image.png 因为给 a 赋初值的时候,为它指定的123 是number 类型的,所以 ts 知道,a是number 类型的,因此,不可以再给 a 赋其他类型的值;

既然不需要给变量显式的类型约束,ts 也可以得到变量的类型,那么,有没有其他情况,需要显示给变量指定类型的,如下:

首先看第一种情况

image.png

因为 sum 函数的返回值类型是不确定的,因此 a 的类型就不确定,所以,需要我们去指定 a 的类型,如下:

image.png

第二种情况

image.png

如果我们的本意是想求 123 + 123 = 246,但是不小心传入了字符串参数,那么得到的结果就会是'123123',并且 js 不会报出任何的错误,这时,就需要显式的指定类型,如下:

image.png

  • 当参数类型不正确时,可以及时发现错误
  • 总结:并不是任何时候都需要给变量明确指定类型,因为 ts 会自动推断。只有 ts 无法正常推断,或者函数形参时,才有必要显式声明

二、对于复杂类型

简述: 复杂类型变量可以通过添加泛型指定类型;

2.1、ts 泛型的基本使用场景
  • 泛型使用场景:在 定义 函数、接口、类 的时候,不能预先确定要使用的数据的类型,而是 在使用的时候才能确定
  • 将类型作为变量使用,即 动态类型
  • 语法:定义时 <大写字母>,多个逗号隔开,如 <T, K, V>
2.2、函数 使用泛型
  • 函数定义时,参数返回值类型 不确定
  • `function name () {}

例1、简单使用(可以参考基础知识函数)

function test<T>(x: T, y: T): T[] {
 return [x, y]
}
​
// 会自动做类型判断,调用时不写类型也行
test<number>(1, 2)
test(1, 2)
test('a''b')
test(falsetrue)

例2、 多个泛型参数、泛型设置默认值

  • 多个泛型之间 , 隔开
  • 可以 定义默认值,如 <T = number>
function getArr <K = number, V = string> (value1: K, value2: V) :[K, V] {
 return [value1, value2]
}
const arr = getArr<stringnumber>('jack'123.321)
console.log('arr', arr) // ['jack', 123.321]// split、toFixed有代码补全
console.log(arr[0].split('')) // ['j', 'a', 'c', 'k']
console.log(arr[1].toFixed(2)) // 123.32
2.3、类型别名type 使用泛型
  • 类型别名使用 type 关键字来定义
  • type name<T> = ...
type A<T> = T
​
let a:A<boolean> = true
let b:A<undefined> = undefined
let c:A<null> = null
let d:A<string> = 'abc'
2.4、接口interface 使用泛型
  • 定义 接口时,为接口中的 属性方法 定义泛型类型
  • interface name <T> {}
// 定义一个接口
interface IBlue <T> {
 msg: T
}
​
// 1、在类中使用
class blue implements IBlue<string> {
 msg:string = '111'
}
​
// 2、普通对象的使用
let dataIXiaoman<boolean> = {
msgfalse
}
2.5、类class 使用泛型
  • 定义一个类,类中的 属性值的类型 不确定,或 方法中的参数返回值的类型 不确定
  • 实例化 类的时候,再确定泛型的类型
  • class name <T> {}
// 例子:
class Methods <T> {
 defaultValue: T
 constructor (defaultValue: T) {
   this.defaultValue = defaultValue
}
 sayHi (msg: T) {
   console.log(msg, this.defaultValue)
}
}
​
// 在实例化类的时候,再确定泛型的类型
// 传入number型
const m1 = new Methods<number>(100)
m1.sayHi(100)
// m1.sayHi('abc') // 报错// 传入string型
const m2 = new Methods<string>('abc')
m2.sayHi('abc')
// m2.sayHi(100) // 报错
2.5、泛型约束
  • 需要 对泛型增加一些 约束条件 时(约束,想到 interface)
  • 语法:泛型 extends 接口
  • 进阶搭配:keyof 可取出对象中所有属性,in 循环

通过几个小例子来理解一下

例1:定义一个函数,返回参数的 length

不是每种类型的参数都有 length 属性,所以我们要 手动限制 当前的 泛型拥有 length 属性

// 定义一个接口,含有 length 属性
interface ILength {
 lengthnumber
}
​
// <T extends ILength>:则 T 必须具有 length 属性 
function getLength <T extends ILength> (x: T):number {
 return x.length
}
​
// 字符串有 length 属性
console.log(getLength('aaaaaa'))  // 6
// 数组有 length 属性
console.log(getLength([123456789])) // 3
// 数值没有 length 属性,报错
// console.log(getLength(123))

例2:定义一个函数,传入对象和 key,返回value

① 简单的写法:仅限制 T 为 object 类型

function getVal <T extends object, K> (obj: T, key: K) {
 return obj[key] // 报错 Type 'K' cannot be used to index type 'T'
}

报错内容:类型“K”不能用于索引类型“T”,或者说,不能完全确定 K 是 T 中的一个key。 那怎么能手动限制 K 一定是 T 的 key 呢?

② 进阶写法:使用 keyof

K extends keyof T,表示拿出 T 中所有的 key 值,作为联合类型 K

// 对泛型 K做出限制(取出T中所有属性,作为一个联合类型,然后让 K extends 它)
function getVal <T extends object, K extends keyof T> (obj: T, key: K) {
 return obj[key]
}
​
let obj = {
 name'blue',
 age15
}
console.log(getVal(obj, 'age'))
// console.log(getVal(obj, 'sex')) // 不存在的key,报错

例3:写一个小工具,让 interface 的每一个属性都变成可选属性(?:)

interface IPer {
 nameString
 agenumber
 sexstring
}

使用 keyof 取出所有的 key, 使用 in 循环

type OPtions <T extends object> = {
[key in keyof T] ?: T[key]
}
type a = Options<IPer>
// 光标放到 a 上 ,显示如下:
// type a = {
//   name?: String;
//   age?: number;
//   sex?: string;
// }

同样的,也可以都改成 readonly 形式

type Readonly <T extends object> = {
 readonly [key in keyof T] : T[key]
}
type b = Readonly<IPer>
// 光标放到 b 上 ,显示如下:
// type b = {
//   readonly name: String;
//   readonly age: number;
//   readonly sex: string;
// }