阅读 57

❤️‍🩹 typescript学习笔记【第二课】类型系统

TS 的核心部分就是类型系统

类型注解使用 :TypeAnnotation 语法。在类型声明空间中可用的任何内容都可以用作类型注解。类型声明关键字 interface type

  • class 类声明的空间可以当作类型注释
  • interface type 声明的类型不可以当作变量使用
  • var let const 等变量声明的空间不可以当作类型注释

简单类型

原始类型

JavaScript 的基础数据类型适用于Typescript的类型系统,主要包含 number string boolean bigInt(typescript3.2) symbol(typescript4.4)

定义以使用规则如下:👇

let num: number;
num = 123;
num = 123.456;
num = '123'; // Error

let symb: symbol;
symb = Symbol(1);
symb = Symbol('abc');
复制代码

特殊类型

any undefined null void never unknow

any

any 在 typescript 的类型中可以兼容各种类型,可以赋值给任意类型的数据。常用于 JavaScript 向 typescript 迁移的时候,在日常TS开发过程中考虑类型保护,应尽量减少使用 any 类型。

let power: any;
let num: number = 21;

// 赋值任意类型
power = '123';
power = 123;
power = num;
复制代码

null 和 undefined

默认情况下 null 和 undefined 是所有类型的子类型。null和undefined可以给任何类型赋值。 但是开启了 strictNullChecks 的情况下 null 和 undefined 只能赋值给 void 和它们各自的类型。

void

any 相反,void 表示不属于任何一个类型,常用于函数中表示没有返回值。

function fun(n:number):void {
    console.log(n);
}
复制代码

💡 void 类型的变量只能赋值为 undefined 或 null ,没有什么意义。

never

never 类型表示永不存在的值的类型,主要用于try.catch

unknow

任何类型数据都可以赋值给 unknown 类型的变量;

unknown 类型数据只能赋值给 unknownany类型变量。

any不同的是👇

const n1:any = {
    foo: 'abc'
};
n1.foo;  // OK

const n1:unknown = {
    foo: 'abc'
};
n1.foo;  // ERROR: Property 'foo' does not exist on type 'unknown'.
复制代码

复杂类型

数组 元祖 枚举 对象

数组 Array

第1种, 使用 [] 指定类型, 表示该数组内的元素都是这种类型:

let numbers:(number)[] = [1,2,3,4];
let numbers:(number|string)[] = [1,2,3,4,'5'];
复制代码

第2种,使用范型 Array<T> 定义

let numbers:Array<number> = [1,2,3,4,5];
复制代码

元祖 Tuple

元祖类型表示一个已知数据类型(顺序)和元素数量的数组。

const list1:[number, string] = [1, '2', 3]; // Error: 数量不对,元祖声明只有两个变量
const list2:[number, string] = [1, 2]; // Error: 第二个元素类型不对
const list3:[number, string] = ['1', 2]; // Error: 两个元素类型反了
const list4:[number, string] = [1, '2']; // OK
复制代码

枚举(enum)

枚举是组织收集有关联变量的一种方式,TypeScript 支持数字的和基于字符串的枚举。其中数字枚举相对字符串枚举多了 “反向映射”。

数字枚举

enum Status { 
    CREATED,
    INPEOCESS,
    CLOSED
}
const status = Status.CREATED;
const created = Status[0];
复制代码

默认情况下,CREATED 值为0,其余成员依次+1,编译之后 👇

var Status;
(function (Status) {
    Status[Status["CREATED"] = 0] = "CREATED";
    Status[Status["INPEOCESS"] = 1] = "INPEOCESS";
    Status[Status["CLOSED"] = 2] = "CLOSED";
})(Status || (Status = {}));
var sts = Status.CREATED;
var created = Status[0];
复制代码

可以设置默认值,或手动赋值。

enum Status { 
    CREATED = 1,
    INPEOCESS,
    CLOSED
}
enum Status { 
    CREATED = 2,
    INPEOCESS = 4,
    CLOSED = 6
}
复制代码

字符串枚举 与数字枚举中手动赋值类似。

enum Status { 
    CREATED = '新建',
    INPEOCESS = '开发中',
    CLOSED = '已关闭'
}
复制代码

编译后

var Status;
(function (Status) {
    Status["CREATED"] = "新建";
    Status["INPEOCESS"] = "开发中";
    Status["CLOSED"] = "已关闭";
})(Status || (Status = {}));
var sts = Status.CREATED;
复制代码

对象(object)

在ts中定义对象类型通常不会使用object,一般会使用接口标注具体的对象类型。

let o2:object = { a:1, b:2 };
复制代码

函数(Function)

function sum(x: number, y: number): number { 
    return x + y 
}
let sum = function(x: number, y: number): number { 
    return x + y 
}
复制代码

接口

接口(interface) 是TS的一个很重要的概念,用来抽象的描述 「对象的形状shape」。可以使用内联注释或接口的形式

// 内联注释
const person: { 
    name: string;
    age: number;
} = {
    name: '张三',
    age: 18
}

// 接口形式
interface Person { 
    name: string;
    age: number;
}
const person: Person = {
    name: '张三',
    age: 18
}
复制代码

另外接口中可以使用readonly只读属性,限制这种类型的变量只有在初始化的时候可以赋值,后面不可以再修改。

interface Person { 
    name: string;
    readonly age: number;
}
const person: Person = {
    name: '张三',
    age: 18
}
person.age ++; // Error
复制代码

当接口的属性是为可选参数时,可以使用 ?: 来指定属性的类型

interface Person { 
    name: string;
    age?: number;
}
const person: Person = {
    name: '张三'
}
复制代码

类型别名

顾名思义就是给类型起一个别名,使用关键字type来定义,和 interface 有很多相似之处,后面会详细介绍两者的区别

type strType = string;
type Person = { 
    name: string;
    readonly age: number;
}
复制代码

高级类型

联合类型

A | B 表示可接受类型A或者类型B。

let numStr: number | string;
numStr = '123';
numStr = 123;
复制代码

交叉类型

A & B 表示可接受的类型必须同时满足A和B两种类型。

interface IPerson {
    name: string
    age: number
}
interface IWorker {
    work: string;
}
type worker = IPerson & IWorker;
const workerPerson: worker = {
    name: '翠花',
    age: 18,
    work: 'coding'
}
复制代码

💡👉 通常用在对象类型,如果交叉交叉两个不同的基本类型会得到一个 never 类型。type nev = string & number; 相当于 type nev = never

索引类型

使用索引类型查询操作符 keyof T 可以获取到类型T的所有public属性名构成联合类型。

class Person { 
    name: string
    age: number
    private lover: string
}
type personKeys = keyof Person
//等同于 type personKeys = "name" | "age"
复制代码

其中T[K]索引访问,获取T所有存在于K的属性的类型组成的联合类型。

function pluck<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
    return keys.map(key => obj[key])
}

interface Person {
    name: string
    age: number
}
let person: Person = {
    name: '李翠华',
    age: 18
}
let strings = pluck(person, ['name', 'age'])
// strings  ['李翠华', 18]
复制代码

type str = Person['name' | 'age'] 等同于 type str = string | number

映射类型

语法 [K in Keys] 和 for...in 类似遍历类型中的所有属性名。

type Keys = 'name' | 'hobby' 
type Person = {
    [K in Keys]: string 
}
复制代码

其中 Keys 可以是 通过 keyof T 获取到的属性联合类型,T[P]就是当前 key 的类型。

class Person { 
    name: string
    age: number
    private lover: string
}
type personKeys = keyof Person
type Optional<T> = {
    [P in keyof T]?: T[P]
}
type personType = Optional<Person>
const testPerson: personType = {
    name: "test",
    age: 123
}
// type personType = Optional<Person> 相当于 👇
// type personType = {
//     name?: string;
//     age?: number;
// }
复制代码

[P in keyof T]?: T[P] 全部设置为可选属性 [P in keyof T]-?: T[P] 全部设置为必须属性

范型

泛型(Generics)是允许同一个函数接受不同类型参数的一种模板。用范型可以似一个组件支持多种类型,来创建可重用的组件。

把类型当作参数传给接口、函数

function identity <T>(value: T) : T {
  return value;
}

console.log(identity<Number>(1)); // 1
console.log(identity(hello)); // hello
console.log(identity<Number>([1,2,3])); // [1,2,3]
复制代码
  • <T> 类型变量可以显式设定
  • identity(hello) 也可以隐式设定,编译器自动选择类型

一些常见的变量名称

  • T(Type) 表示TS类型
  • K(Key) 表示对象中的键类型
  • V(Value) 表示对象中的值类型
  • E(Element) 表示元素类型

参考文档

Exclude Extract Pick Omit

Exclude(排除)

type Exclude<T, U> = T extends U ? never : T
复制代码

Exclude<T, U>从联合类型T中排除存在于联合类型U之后的联合类型。

  • type N = Exclude<'a' | 'b' | 'c', 'a' | 'b' | 'c'>,结果为:type N = never
  • type E = Exclude<'a' | 'b' | 'd', 'b' | 'c'>,结果为:type E = 'a' | 'd'

Extract(提取)

type Extract<T, U> = T extends U ? T : never
复制代码

Extract<T, U> 提取同时存在于联合类型T和联合类型U的联合类型。

  • type N = Extract<'a' | 'b' | 'c', 'd' | 'e'>,这次我们得到的结果是type N = never
  • type E = Extract<'a' | 'b' | 'd', 'b' | 'c'>,这次我们得到的结果是type E = 'b'

Pick(筛选)

type Pick<T, K extends keyof T> = {
    [P in K]: T[P]
}
复制代码

Pick<T, K>Pick<T, K extends keyof T>从 对象类型T中筛选出存在于联合类型K中的属性,最终得到对象类型。

  • K extends keyof T 是为了限定联合类型K必须存在于对象类型T
  • 若联合类型K中包含了对象类型T中不存在的属性,最终生成的属性类型为 unknown
interface IPerson {
    name: string
    age: number
}
type pick = Pick<IPerson, 'name' | 'age' | 'age1'>
// 相当于
// type pick = {
//   name: string;
//    age: number;
//   age1: unknown;
// }
复制代码

Omit(过滤)

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
复制代码

Omit<T, K>就是从对象类型T中将联合类型K中所有属性过滤掉,最终得到对象类型。

interface IPerson {
    name: string
    age: number
}
type omitT = Omit<IPerson, 'name'>
// 实际上omitT类型为 👇
// type omitT = {
//     age: number;
// }
复制代码

类型断言

类型断言(Type Assertion)就是手动指定一个值的类型。

as

let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
复制代码

尖括号

let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
复制代码
文章分类
前端
文章标签