likes
comments
collection
share

TypeScript 中的类型(下)

作者站长头像
站长
· 阅读数 22

何时用 enum 类型

映射

在后端数据库里存着 1, 2, 3, 4; 在文档里分别映射代表的含义

status: 1 todo
        2 done
        3 archived
        4 deleted

下载 status: 1 在前端界面显示着

+ 未完成
- 已完成
- 归档
- 不做了

当用户选择了已完成,那就会把 JSON 是 status: 2 这个结果上传给数据库。

但是我们在写代码的时候经常会忘记 每个数字代表的含义 这个时候我们就可以使用 enum

enum A {
  // todo = 1, 表示里面的枚举值,它的映射对应的真实值
  todo = 1,
  // 不写就默认每个 1++
  done,
  archived,
  deleted
}

//  let _status: A = 1; 会被类型擦除成以下代码
let _status = 1;
// A.todo 就是 1, 类型擦除时 A.todo 会变为 1
_status = A.todo;  // _status = 1
_status = A.done;
console.log(_status);

A.done 就会被类型擦除成 _status = 2 TypeScript 中的类型(下)

权限控制

有的时候有一些权限的控制

enum Permission {
  // 用二进制位表示权限
  None = 0,
  Read = 1 << 0,  // 0001
  Write = 1 << 1,  // 0010
  Delete = 1 << 2,  // 0011
  Manage = Read | Write | Delete   // 0111
}

// 若 a & b === b,则 a 有 b 的所有 1
const user: { permission: Permission } = { permission: 0b0101 };
if (canWrite(user.permission)) {
  console.log("有写权限")
}
if (canManage(user.permission)) {
  console.lof("有管理权限")
}

function canWrite(p: Permission) {
  return (p & Permission.Write) === Permission.Write
}

function canManage(p: Permission) {
  return (p & Permission.Manage) === Permission.Manage
}

何时不用enum

// 以下使用 string enum 的时候 就很呆
enum Fruit {
  apple: 'apple',
  banana = 'banana',
  pineapple = 'pineapple',
  watermelon = 'watermelon'
}

let f = Fruit.apple;
f = Fruit.pineapple
console.log(f)
type Fruit = 'apple' | 'banana' | 'pineapple' | 'watermelon'
let f: Fruit = 'apple'
f = 'watermelon'
console.log(f)

结论

当你使用 number enum 的时候是可以的, 不要使用 string enum 的时候,使用 other enum 的时候也不行。

type 与 interface 的两个区别

何时用 type ?

  • 几乎都可以用 type

  • 类型别名: Type Aliases

  • 作用:给其它类型取个名字

type Name = string
type FalseLike = '' | 0 | false | null | umdefined
type Point = { x: number; y: number }
type Points = Point[]
type Line = [Point, Point]
type Circle = { center: Point; radius: number }
type Fn = (a: number, b: number) => number
type FnWithProps = {
  (a: number, b: number): number
  prop1: number
}

TS 中花括号里的函数只能用冒号来表示。

// 直接这样写,它是没有属性的,它就是一个纯函数没有任何额外的属性
type FnWithNoProp = () => {}

// 之前写函数
type Fn = () => void

// 如果写成对象的形式, 它是有属性的
// 以下为带有属性的函数声明方式
type FnWithProp = {
  // 表示函数
  (a: number, b: number): number
  prop: string
}

const f: FnWithProp = (x, y) => {
  return x + y
}

// 声明的时候,后面必须带一个赋值
f.prop = 'hi'
console.log(f) 

为何叫别名

// A 没有被记住
type A = string // A 是 string 的一个外号(别名)
type B = A // B 也是 string 的一个外号

type 声明的是一个别名,并不是真正的生成一个新的类型。

何时用 interface ?

  • 用来 声明接口
  • 描述对象的属性 ( declare the shapes of objects )

面向对象编程(OOP)

  • class 在JS里是 构造函数
  • 方法 在JS里是 函数
  • 继承 在ES6 之前用的是原型

面向对象它会把你熟悉的基本编程知识重新取一个名字

TS 的 interface 不但可以描述功能还能描述属性

描述对象包括函数和非函数

interface A {
  a: number
  b: string
}

// 索引签名,我对你的索引进行批量的描述
interface B {
  [k: string]: number
}

// 描述数组

//现在这个 C 就可以代表所有 string 的数组
interface C extend Array<string> { }

// type 对应的的写法
type D = Array<string>
interface X { 
  age: number 
}

type A1  = Array<string> & {
  name: string
} & X

interface A2 extends Array<string>, X {
  name: string
}

interface 就是用面向对象的黑话把我们 type 能做到事情再重新描述一遍(并不是100%)

insterface 描述函数

interface Fn {
  (a: number, b: number): number
  xxx: number
}

const f:Fn = (x, y) => {
  return x + y
}
f.xxx = 101

// 描述日期
interface D extends Date {
  xxx: number
}

const d: D = new Date()
d.xxx = 11 // 这样写不行,因为只有函数能够后加属性,如果要直接用的话需要自己写一个构造函数
console.log(d)

interface R extends RegExp {}


// 基本上 interface 可以描述所有的对象

type 描述: undefined、string、number、boolean、bigint、symbol、null、interface

interface 描述:普通对象、Array、Function、Date、Map、Set ...

type V.S interface

  • 区别1: interface 只描述对象,type 则描述所有数据

  • 区别2:type 只是别名,interface 则是类型声明

// 给 string 取了个别名 A
typa A = string
type B = A  // B = string 跳过了 A

// 声明了一个 D 的类型
interface D extends Date{
}

type E = D // E 的类型是 D

type 与 interface 的第三个区别

type 不可重新赋值

type A = number // 从此之后你不能改变 A 的值,就好像 const
A = string // 报错

优点:TS 可以提升它的计算效率

缺点:不好扩展

interface 自动合并

axios.get('./xxx', {
  _autoLoading: true,
  _mock: 'xxx'
})

扩展已经存在的 interface

// 扩展 axios
import {AxiosRequestConfig} from 'axios'
declare module 'axios' {
  export interface AxiosRequestConfig {
    _autoLoading?: boolean
    _mock?: string
  }
}
// 当声明两个 interface,它不会覆盖,而是会合并
interface X {
  name: string
}
interface X {
  age: number
}

const a: X {
  name: 'hone',
  age: 18
}

为什么 interface 可以合并, type 不能合并

因为 interface 只描述对象,你在对象上加一个属性,不影响之前的代码。

type 不一定是对象,无法扩展。

那 包装类型 呢?

可以扩展一些基本类型,但是不能通过 type 来扩展, 需要通过 interface 来扩展。

// 扩展全局的 string
declare global { // 声明全局范围
  interface String {
    padZero(x: string) : void // 然后再写它有一个新的属性
  }
}

const s = 'hi'
s.padZero('hi') // 不会报错

interface 存在的价值:通过 interface 你就可以去给全局的任何一个接口,添加上你要加的东西,不要修改 interfae 已有的属性。

  • 区别3: 对外 API 尽量用 interface, 方便扩展,对内 API 尽量用 type,防止代码分散

type 可以继承!!!

面向对象所有的黑话都是把你见过的东西重新命名了。

继承的本质就是我这个拥有另外一个对象的属性或者我这个类拥有另外一个类的属性。

一个东西拥有另外一个东西的属性是很容易实现的,比如说 复制(遍历你的属性全部复制过来)。

// 把 A 扩展了一个属性叫做 bbb}
type A = {
  aaa: string
}

type B = {
  bbb: string
} & A

// 已经继承了
const b: B = {
  aaa: 'yes',
  bbb: 'yes'
} 

void类型

表示一个函数并不会返回任何值,当函数并没有任何返回值,或者返回不了明确的值的时候,就应该用这种类型。

// 上篇 在 playcode.io 上这样写是会报错的
type  Fn = () => void  // 这里声明的函数是没有返回值的
const f: Fn = () => {
  // 有些环境中写成以下几种都不会报错
  return undefined
  // return 1
  // return null
}

const a = f() // 只要用了 void 就用不来 return 得到的东西 a: void
// 可以进行断言
;(a as any).toString() // 断言就可以随便用

void 它不会严格的去检查

官方文档 return-type-void 想要看什么属性,一般搜索然后看 Hoodbook 章节

type voidFunc = () => void;

const f1: voidFunc = () => {
  return true;
};

const f2: voidFunc = () => trueconst f3: voidFunc = function () {
  return true;
};

// 得到的都是 void
const v1 = f1();
const v2 = f2();
const v3 = f3();

都可以有多余的 return 但是用不了

TypeScript 中的类型(下)

// 以下这种写法就不可以 return 任何东西了
function f(a: number): void {
  return 1
}