前言
通过上一篇文章juejin.cn/post/762178… ,我们对 TypeScript 有了充分的认识,本篇我们开始学习 TypeScript 的基础部分。
基础类型
- string:字符串
- number:数值
- boolean:布尔值
- null:没有值
- undefined:未定义
- symbol:唯一标识
如何定义一个类型?
let str: string = "hello"
含义:声明一个变量并约束它为字符串类型。使用冒号 : 来约束,这种变量类型的约束方式称为类型注解。
冒号后边跟的类型是当前变量的约束类型,赋值时就必须和约束的类型相符,否则会编译报错,如下:
let str1: string = 123 // 报错,不能将 number 类型值赋给 string 类型的变量
在类型注解之上还有另外两种类型的定义方式,类型别名和接口类型,
类型别名
type StrVariable = string
let str: StrVariable = "hello"
上方代码使用 type 关键字来声明一个类型为 string 的类型别名 StrVariable,可以理解为只是给 string 类型起了一个别名叫 StrVariable,这个类型别名同样以类型注解方式来约束变量 str 定义时的类型。
接口类型
interface Person {
name: string,
age: number
}
const p1: Person = {
name: "luckyCover",
age: 11
}
上面代码定义了 Person 接口,包含 string 类型的 name 和 number 类型 age 属性,同样使用类型注解的方式来约束 p1 对象,那么 p1 对象上就必须要有 name 和 age 属性,而且属性对应类型也得相符,否则编译会报错。
为此,我们知道了类型注解、类型别名、接口类型的含义及其关系。
undefined 表示一个未定义的变量。
let UndefinedVariable: undefined
null 表示一个没有值的变量,一般用于对象的初始化。
let NullableVariable: null
其中,用 undefined 约束的变量可以赋值为 undefined/null,
同样用 null 约束的变量也可以赋值为 null/undefined。赋予其他类型的值会报错。
let UndefinedVariable: undefined
UndefinedVariable = null
let NullableVariable: null
NullableVariable = undefined
UndefinedVariable = 11 // 编译报错
NullableVariable = '22' // 编译报错
图中可以得知,使用类型注解约束变量为 undefined/null 类型时,变量仅能赋值为 undefined/null。
特殊类型
- unknown:未知类型
- any:任意类型
- never:永远不会返回的类型
- void:无返回值
使用 unknown 和 any 的类型注解来约束变量,变量在初始化后仍可以赋值为任意类型的值,这也是它们相似的点。
let unknownVariable: unknown = 1
unknownVariable = '1'
unknownVariable = true
let anyVariable: any = 1
anyVariable = '1'
anyVariable = true
上边代码都不会报错。
声明一个没有类型约束的普通变量,其默认类型也是 any,如下:
对使用 unknown 约束的变量直接操作时,会报错
原因是你声明的是一个未知的类型,这时候需要
类型断言或类型检查来告知。
let unknownVariable: unknown = 'a'
if(typeof unknownVariable === 'string') { // 类型检查
console.log(unknownVariable.toUpperCase());
}
console.log((unknownVariable as string).toUpperCase()); // 类型断言
上边代码是使用 unknown 类型约束的变量,操作前必须进行 类型检查或类型断言,这也是 unknown 比 any 要安全的原因,因此开发时建议优先使用 unknown。
从图中可以看出,unknown 类型变量在操作时多了类型检查/类型断言这一步,这能很好避免错误的类型操作,比如某个变量类型约束为 any,你在某一地方赋值为 number 类型的值,但你以为它是字符串类型的值,于是使用字符串上的 toUpperCase() 方法,但实际变量是数值类型的的值,这在编译阶段是不会报错的,如下图所示:
上边代码很可能要在运行阶段才能发现问题,而如果使用 unknown 类型来约束,在编译阶段就能发现问题并采用类型检查或类型断言来解决。
never 表示永远不会返回/不存在的值,主要用于约束抛出错误的函数、无限循环的分支。
// 抛出错误的函数
function ThrowErrorFuc(x): never {
throw new Error('hhh')
}
// 无限循环的分支
function InfinityBranch(): never {
while(true) {
console.log('infinite loop');
}
}
void 用于约束没有返回值/返回值为空的函数的类型。
function voidFunc(): void {
console.log('no return anything');
}
function voidFunc(): void {
return null
}
function voidFunc(): void {
return undefined
}
其他类型
数组
数组类型的写法有多种,第一种是类型+方括号写法,也称为普通数组写法:
let numberArr: number[] = [1, 2, 3]
numberArr[3] = 6 // [1, 2, 3, 6]
let stringArr: string[] = ['a', 'b', 'c']
第二种是泛型写法:
// 泛型写法
let stringArr: Array<string> = ['a', 'b', 'c']
stringArr[3] = 'd'
第三种是联合类型写法:
let togetherArr: (number | string)[] = [1, 'a'] // 可指定多种类型的数组元素
togetherArr.push(6)
元组
let numberTupple: [number, string] = [1, 'a']
numberTupple[2] = 2 // 编译报错
针对数组和元组,我们可以对比下两者的区别。
接口类型
使用 interface 关键字定义接口,通常用于对象、类
interface Person {
name: string,
age: number
}
// 对象应用接口
const p: Person = {
name: 'luckyCover',
age: 11
}
interface Animal {
name: string,
bark: () => void
}
// 类实现接口
class Dog implements Animal {
name: 'dog'
bark() {
console.log('汪汪汪');
}
}
interface Runnable {
run: () => void
}
// 一个类实现多个接口
class Dog implements Animal, Runnable {
name: 'dog'
bark() {
console.log('汪汪汪');
}
run() {
console.log('跑跑跑');
};
}
上边代码可以看出,对象使用类型注解应用接口,类使用implement实现接口。
接口合并:
interface Person {
name: string,
age: number
}
interface Person {
sex: string
}
// 相当于
interface Person {
name: string,
age: number,
sex: string
}
接口合并允许将多个同名接口中的属性合在一起。
类型别名
使用 type 关键字定义,常用于联合类型、类型合并(交叉类型)、字符串字面量类型
联合类型:
type combineType = string | number
const strOrNum: combineType = 1 // 可以赋值为 string/number 类型的值
类型合并(也称交叉类型):
type Person = {
name: string,
age: number
}
type Hobby = {
write: () => void
}
type Student = Person & Hobby
const s1: Student = {
name: 'luckyCover',
age: 11,
write() {
console.log('do homework');
}
}
字符串字面量类型:
type PromiseStatusType = 'fulfilled' | 'pending' | 'rejected'
const pStatusType: PromiseStatusType = 'fulfilled' // 只能是上边字符串变量类型中的某个字符
字符串字面量类型限定变量的取值只能为声明的联合取值中的某一个
扩展
泛型
泛型可用于类、函数和接口中,它也可称为类型变量或类型参数,因为它在定义时不确定具体的类型,而是在使用时确定,如下例子:
interface Person<T> {
name: string,
age: T
}
let p: Person<number> = {
name: 'luckyCover',
age: 11
}
定义的 Person 接口后边 <T> 表示定义一个类型参数(变量)的泛型 T,然后应用于 age 属性,在 p 变量使用时才指定具体的类型为 number。
枚举
枚举用来表示取值限定在特定范围中
enum Fruit {
apple = 0,
orange = 1,
banana = 2
}
console.log(Fruit.apple); // 0
类型推断
在没有给变量指定具体类型时,ts 会根据变量值自动推断该变量的类型。
自动类型推断,比如下列代码:
// ts 会根据 num 变量取值自动推断其属于 number 类型
let num = 123
let value: string | number
value = '123'
console.log(value.length); // 编译正常
value = 123
console.log(value.length); // 编译报错
value 赋值为字符串 123 时,ts 会推断 value 为字符串类型,访问 length 属性正常,但是下边将 value 赋值为数字 123,由于数字类型没有 length 属性,因而会编译报错。
类型断言
function getValue(value: string | number): number {
if(true) {
return (value as string).length
}
}
上边 getValue 函数接收的 value 参数是个联合类型,那么函数体内我们需要在它为 string 类型时读取并返回它的长度,可以使用 value as string 来断言 value 为 string 类型。
总结
通过本篇文章,我们认识了 ts 中的基础类型,知道它们怎么定义和使用,可以直接通过类型注解应用于变量,也可以通过定义类型别名或接口类型,然后使用类型注解应用于变量或对象。其次,我们还了解了其他的一些类型含义以及基本使用方式,比如有数组、元组、泛型、枚举、字符串字面量类型、联合类型,最后我们知道 ts 有类型断言和类型推断。通过学习这些知识,我们对 ts 基础类型的定义和使用方式有了初步的掌握,为后续学习高级用法打下基础。
我是 luckyCover,我正在持续更新 TypeScript 学习系列的文章,欢迎大家一起讨论学习呀~