TypeScript中的复杂类型 | 8月更文挑战

3,172 阅读8分钟

此篇内容为本人学习《TypeScript编程》和拉勾教育《TypeScript 入门实战笔记》及个人总结

数组

在 TypeScript 中,我们也可以像 JavaScript 一样定义数组类型,并且指定数组元素的类型。

TypeScript定义语法有两种形式:

let 变量名:<type>[] = [...]
let 变量名:Array<type> = [...]

示例:

/**
 * 第一种方式定义数组
 */
// 子元素是数字类型的数组
let arrayOfNumber: number[] = [1, 2, 3];
// 子元素是字符串类型的数组
let arrayOfString: string[] = ['x', 'y', 'z'];


/**
 * 第二种方式定义数组
 */
// 子元素是数字类型的数组
let arrayOfNumber: Array<number> = [1, 2, 3];
// 子元素是字符串类型的数组
let arrayOfString: Array<string> = ['x', 'y', 'z'];

以上两种方式都可以定义数组,本质上没有任何区别,具体采用那种形式,取决于个人习惯或者团队规范要求而定

元组

元组是array的子类型,是定义数组的一种特殊方式,长度固定,各索引位上的值具有固定的已知类型;元组最重要的特性是可以限制数组元素的个数和类型,它特别适合用来实现多值返回

在 JavaScript 中并没有元组的概念,作为一门动态类型语言,它的优势是天然支持多类型元素数组

let fruit:[string, number, boolean] = ["Apple", 2, false]

元组也支持可选的和剩余元素,与在对象的类型中一样,?表示可选

  • 可选元素

    let trainFars: [number, number?][] = [
        [10, 23.5],
        [2, 6.28],
        [9.42]
    ]
    

    上面代码表示二维数组中的元素可以是数组类型的一个元素或两个元素

  • 剩余元素

    // 至少有一个元素
    let friends:[string, ...string[]] = ["Forest", "Sare", "Tali", "Chloe"]
    
    // 不同类型的元素列表
    let list:[number, boolean, ...string[]] = [23, false, "a", "b", "c"]
    

只读数组和元组

常规的数组是可变的(可以使用.push.splice等方法更新数组);不过我们又是希望数组不可变,修改之后得到的新的数组,而原数组没有变化。TypeScript原生支持只读数组类型,用于创建不可变的数组。只读数组与常规的数组没有多大差别,只是不能就地更改;若想创建只读数组,要显示注解类型;若想更改只读数组,使用非变形方法,例如:.concat().slice(),不能使用可变型方法,例如:.push().splice

let args: readonly number[] = [1, 2, 3, 4, 5]
let nums: readonly number[] = args.concat(4, 5, 6)
console.log('args:', args) // args: [ 1, 2, 3, 4, 5 ]
console.log('nums:', nums) // nums: [1, 2, 3, 4, 5, 4, 5, 6]

// 修改元素
args[2] = 10 // 报错 类型“readonly number[]”中的索引签名仅允许读取

// 添加数据
args.push(30) // 报错 类型“readonly number[]”上不存在属性“push”

注解数组类型还可以使用Array;类似声明制度数组和元组,也可以使用长格式语法:

type letter = readonly string[]
type str = ReadonlyArray<string>
type list = Readonly<string[]>

// 多类型声明
type user = readonly [number, string]
type fruit = Readonly<[number, string]>

object

object 类型表示非原始类型的类型,即非 numberstringbooleanbigintsymbolnullundefined 的类型。然而,它也是个没有什么用武之地的类型,如下所示的一个应用场景是用来表示 Object.create 的类型。

declare function create(o: object | null): any;
create({}); // ok
create(() => null); // ok
create(2); // 编译报错
create('string'); // 编译报错

TypeScript中声明对象类型的四种方式

  • 对象字面量表示法(例如:{a: string}),也称对象的结构。如果知道对象有哪些字段,或者对象的值都为相同的类型,使用这种方式
  • 空对象字面量表示法({})。尽量避免使用这种方式
  • object类型。如果需要一个对象,但对对象的字段没有要求,使用这种方式
  • Object类型。尽量使用这种方式

枚举

枚举的作用是列举类型中包含的各个值;这是一个无序数据结构,把键映射到值上;枚举可以理解为编译时固定的对象,访问键时,TypeScript将检查指定的键是否存在

枚举分两种:

  • 字符串与字符串之间的映射
  • 字符串与数字之间的映射
enum Language {
  English,
  Spanish,
  Russian
}

按约定,枚举名称为大写的单数形式;枚举中的键也为大写

TypeScript可以自动为枚举中的各个成员导出对应的数字,你也可以自己手动设置

上面的代码经ts推导后可得到如下结果:

enum Language {
  English = 0,
  Spanish = 1,
  Russian = 2
}

枚举中的值使用点号或者方括号表示法访问,就像访问对象中的值一样;如果访问的值不存的话,就会报错,不过一般都直接在编辑器显示出来

let english = Language.English  // 点号访问
let spanish = Language['Spanish'] // 中括号访问

成员的值也可以经计算得出,而且不必为所有成员都赋值

enum Language {
  English = 100,
  Spanish = 200,
  Russian		// 此时如果不手动设置固定值时,Russian默认为 201,也就是上一个枚举成员的下一个数
}

枚举的值混合使用:

enum Colors {
  Red = '#c10000',
  Blue = '#007ac1',
  Pink = 0xc10050,	// 十六进制字面量
  White = 255				// 十进制
}

为了避免不安全的访问操作,可以通过const enum指定使用枚举的安全子集;但是const enum不允许反向查找,行为与常规的JavaScript对象很想;如下:

const enum Language {
  English,
  Spanish,
  Russian
}

// 访问一个有效的枚举键
let english = Language.English // 0

// 访问一个不存在的键
let US = Language.US // 报错

类型推断

在 TypeScript 中,类型标注声明是在变量之后(即类型后置),它不像 Java、C 语言一样,先声明变量的类型,再声明变量的名称

使用类型标注后置的好处是编译器可以通过代码所在的上下文推导其对应的类型,无须再声明变量类型,具体示例如下:

let x1 = 42; // 推断出 x1 的类型是 number
let x2: number = x1; // ok

在 TypeScript 中,具有初始化值的变量、有默认值的函数参数、函数返回的类型都可以根据上下文推断出来。比如我们能根据 return 语句推断函数返回的类型,如下代码所示:

// 根据参数的类型,推断出返回值的类型也是 number
function add1(a: number, b: number) {
  return a + b;
}
const x1= add1(1, 1); // 推断出 x1 的类型也是 number

// 推断参数 b 的类型是数字或者 undefined,返回值的类型也是数字
function add2(a: number, b = 1) {
  return a + b;
}
const x2 = add2(1);
const x3 = add2(1, '1'); // 报错 Argument of type '"1"' is not assignable to parameter of type 'number | undefined

字面量类型

在 TypeScript 中,字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型

TypeScript 支持 3 种字面量类型:

  • 字符串字面量类型
  • 数字字面量类型
  • 布尔字面量类型
let specifiedStr: 'this is string' = 'this is string';
let specifiedNum: 1 = 1;
let specifiedBoolean: true = true;

字面量类型是集合类型的子类型,它是集合类型的一种更具体的表达。比如 'this is string' (这里表示一个字符串字面量类型)类型是 string 类型(确切地说是 string 类型的子类型),而 string 类型不一定是 'this is string'(这里表示一个字符串字面量类型)类型,如下具体示例:

let specifiedStr: 'this is string' = 'this is string';
let str: string = 'any string';
specifiedStr = str; // 报错  类型 '"string"' 不能赋值给类型 'this is string'
str = specifiedStr; // ok 

字符串字面量类型

一般来说,我们可以使用一个字符串字面量类型作为变量的类型,如下代码所示:

let hello: 'hello' = 'hello';
hello = 'hi'; // 报错  Type '"hi"' is not assignable to type '"hello"'

通过使用字面量类型组合的联合类型,我们可以限制函数的参数为指定的字面量类型集合,然后编译器会检查参数是否是指定的字面量类型集合里的成员。

因此,相较于使用 string 类型,使用字面量类型(组合的联合类型)可以将函数的参数限定为更具体的类型。这不仅提升了程序的可读性,还保证了函数的参数类型,可谓一举两得。

数字字面量类型及布尔字面量类型

数字字面量类型和布尔字面量类型的使用与字符串字面量类型的使用类似,我们可以使用字面量组合的联合类型将函数的参数限定为更具体的类型,比如声明如下所示的一个类型:

interface Config {
    size: 'small' | 'big';
    isEnable:  true | false;
    margin: 0 | 2 | 4;
}

在上述代码中,我们限定了 size 属性为字符串字面量类型 'small' | 'big',isEnable 属性为布尔字面量类型 true | false(布尔字面量只包含 true 和 false,true | false 的组合跟直接使用 boolean 没有区别),margin 属性为数字字面量类型 0 | 2 | 4。

类型别名

我们可以使用变量声明为值声明别名,类似地,还可以为类型声明别名。比如:

type Age = number

type Person = {
  name: string;
  age: Age;
  email: string;
}

Age就是一个number,TypeScript无法推导类型别名,因此必须显示注解:

type Age = number

type Person = {
    name: string
    age: Age
    email: string
}

let age: Age = 22
let forest: Person = {
    name: 'Forest',
    age,
    email: '767425412@qq.com'
}

使用类型别名地地方都可以替换成源类型,程序的语义不受影响