Typescript 快速手册 2.0
努力学习 🏞️🏞️
基础知识
启动
生成配置文件 ts.config.json
1 | tsc --init |
监听文件变化
1 | tsc -w index.ts |
安装 ts-node 直接运行 ts 文件,不用先手动编译
1 | npm install ts-node -g |
类型、接口
基础
类型的级别(从顶级到低级排)
- any, unknown
- Object
- Number, String, Boolean
- number, string, boolean
- 1, ‘hello’, false
- never
其中:
any
: 任意类型,可给随便赋值unknown
: 不知道的类型,只能给自己或者 any 赋值
unknown
的对象是无法读取属性的
1 | let person: unknown = { name: "Jerry", age: 18 }; |
- 所以
unknown
类型会比较安全,当不知道用什么类型的时候可以选它
Object
, object
, {}
的区别:🙃
▶️ Object:在原型链上,可包含所有
1 | let test: Object = 1; |
▶️ object:代表非原始类型的类型(引用类型),比较常用于泛型约束
▶️ {}:相当于new Object
,和Object
一样,但是变量不能复制
1 | let person: {} = { name: "Jerry" }; |
接口
- 两个重名的 interface 会合并起来
- 多行时,属性之间可以用
;
,也可以不用
索引签名(index signature)
的可能使用场景:后端返回对象,但是前端只想要其中的几个属性
1 | interface Person { |
1 | interface Fn { |
interface 里面有唯一 id 或者函数,可以用
readonly
限制一下(在对应 key 前面加上)
数组接口 的几种使用方式
1 | let name1: string[] = ["Tom", "Jerry"]; |
二维数组或者更多
1 | let name1: string[][] = [[{ name: "Tom" }], [{ name: "Jerry" }]]; |
如果数组里面啥都有。直接用any
或者用元组
1 | let name: any[] = [1, "qaq"]; |
_剩余参数_:有属于自己的接口
1 | function func(...args: string[]) { |
类型别名
就是用type
来指定某个类型
1 | type s = string; |
interface
不能使用联合类型,但可以使用extends
- 遇到重复的会合并
type
没有继承,但可以使用联合类型,如type s = string | number
- 遇到重复的不会合并
🤤type
的别的用法
1 | // 左边的值会作为右边类型的子类型 |
- 这里是 1
- type 中的 extends 是包含的意思
类型断言、交叉类型、联合类型
1 | let fn = function (type: number): boolean { |
!0
是true
,再!
就是false
了,这时类型就从number
变成了boolean
,但是表达的意义还是对、错(例如:有时后端返回数字而不是 true false 的时候)
联合类型,interface
的话,使用一个&
来联合,是一个。
类型断言,就是那个as
,可以用来欺骗 TypeScript,如果有错的话,编译的时候还是会报错的。另外的写法是<类型>变量
1 | (window as any).abc = 123; |
- any 可以 断言为任何类型
函数
1 | function add1(a: number, b: number): number; |
对于函数的一个参数:默认值 和 可选 不能一起用
在定义函数的时候,第一个参数可以定义this
,用来指定函数当前所处的对象
- 在调用的时候,
this
不用传递
1 | interface Obj { |
- 这个语法在
js
中不能使用
函数重载
可能场景
1 | let user: number[] = [1, 2, 3]; |
class
implements
:用来实现接口,约束类的结构
1 | interface Options { |
extends
:用来继承类
- extends 需要写在 implements 前面,因为 extends 会继承 implements 的接口
- super 原理是父类的
prototype.constructor.call
- 里面的参数是父类的参数如果有个话,需要向里面传递实参
- 可以直接通过
super.
来调用父类的方法或者属性
1 | class Dom { |
其他关键字:
readonly
:只能在声明属性的时候或者构造函数中赋值private
: 类内部使用protected
: 类内部和子类使用public
: 类内部、子类、类外部都可以使用static
: 静态属性,可以直接通过类名来调用。- 只能调用静态的属性和方法,因为不需要实例化,自然就不在实例上面了
- 想要数据的话,可以把数据作为函数的参数传进来
get
、set
: 可拦截属性的读取和赋值操作
1 | class Vue { |
abstract
:抽象类,不能被实例化,只能被继承 🤤
1 | abstract class Dom { |
- 下面实现的叫派生类,抽象类中的东西,派生类必须实现
内置对象
👉🙃 有规可循
ECMA
1 | let num: Number = new Number(1); |
- 一般是 new 什么,就是什么类型
DOM
一般是什么HTMLxxxElement
section、header 这种,会归为 HTMLElement
querySelectorAll
这种,就是NodeList
,如果里面是有几个的,不固定的,使用NodeListOf
1 | let div: NodeListOf<HTMLDivElement | HTMLElement> = |
BOM
1 | let local: Storage = localStorage; |
元组、枚举
元组
1 | let arr: [number, boolean] = [1, false]; |
其他限制:只读、指定名称、可选
1 | let arr: readonly [x: number, y?: boolean] = [1]; |
使用场景:例如一个excel表格
1 | let excel: [string, string, number][] = [ |
❔ 要获取元组中某一个的类型
1 | type first = (typeof arr)[0]; |
❔ 要获取它的长度
1 | type first = (typeof arr)["length"]; |
枚举
1 | enum Color { |
给它指定值
1 | enum Color { |
普通枚举和使用const
定义的枚举
1 | enum Test { |
会被编译成一个对象
1 | var Test; |
加了const
的,会直接编译称一个常量 ??
但是我的编译出一个空白文档。typescript 5.1.6
👉 反向映射 通过 value 获得 key,正向反之
1 | enum Types { |
never、symbol
never
永远无法到达的类型 🙃
1 | type A = string & number; |
type A
就是一个never
类型
如果在联合类型中使用到never
,是会直接被忽略掉的
常用的场景:兜底逻辑,直接代码报错,找到问题所在
1 | type A = "唱" | "跳" | "rap"; |
- 默认值得时候,
value
会是never
类型
这样的话代码没有问题。然后以后要改的时候,突然想在A
中再加上类型'篮球'
,这样代码就会报错,因为这时候default
就会是一个string
篮球。string
无法赋值给never
这样的话防止代码多了,都不知道错误在哪
symbol
- 可避免对象中重复出现的key
for in、Object.keys 不能读取到 symbol
每一个都是唯一的
1 | let a1: symbol = Symbol(1); |
😅 就是想要得到两个相等的???
1 | console.log(Symbol.for("xiaoman") == Symbol.for("xiaoman")); |
使用
.for
会在全局的symbol中找有没有注册过这个key- 有的话直接拿来用
- 没有的话,先创建一个
Object.getOwnPropertyNames
:获取对象中普通的属性名
Object.getOwnPropertySymbols
:获取对象中 symbol 类型的属性
Reflect.ownKeys
:上面两个都可同时获取
配置、声明
命名空间
ts 和 es6 中,包含顶级import
或者export
的文件会被当成一个模块,反之文件里面的内容被视为全局可见
这样的话,在两个不同的文件中声明相同的变量,会报错说变量已经存在
📄index.ts
1 | const a = 1; |
📄index2.ts
1 | const a = 1; |
要解决这个问题,可以将这个文件变成一个模块
1 | export const a = 1; |
除了这个方法,还可以使用 👉命名空间
📄index.ts
1 | namespace A { |
📄index2.ts
1 | namespace B { |
使用的时候,和对象一样
1 | console.log(A.a); |
- 命名空间还可以嵌套,在里面
export namespace
- 最外层导出的命名空间,通过
import
来导入,import {xx} from './index.ts'
- 还可以这样导入
import x = B.C
重名的命名空间会合成一个
三斜线指令
- 常见的是用到引入依赖
1 | /// <reference path="index2.ts" /> |
在编译的时候开启removeCommoent
的选项,编译后的文件就会移除掉这个引用声明的注释
声明文件
xx.d.ts
文件
有些库安装的时候没有声明文件,可以尝试安装一下,也有可能是真的没有声明文件
1 | npm i --save-dev @types/xxx |
@types
开头是一个规范来的
没有的话,自己手写一下咯。要为谁写声明文件,就以这个包的名字为声明文件的名字,比如express 没有声明文件,那就应该是express.d.ts
1 | declare module "express" { |
1 | declare const a: number; |
如果文件里面没有
import
或者export
的话,也需要指定一下export{}
来使得 ts 能把当前文件识别为模块
泛型
基础
在函数中使用
代码逻辑一样,但想返回不同的类型。如果没有使用泛型,那只能赋值两份代码,不方便,所以要使用动态类型
1 | function func1(a:number, b:number)Array<number> { |
使用泛型
1 | function func<T>(a: T, b: T): Array<T> { |
- 不用完整的调用形态,因为可以类型推断
在type
中使用
1 | type A<T> = string | number | T; |
在interface
中使用
1 | interface Obj<T> { |
使用多个泛型
1 | function func<T, K>(a: T, b: K): Array<T | K> { |
泛型也可以使用默认值:函数不传值,泛型使用默认泛型
1 | function func<T = number, K = string>(a: T, b: K): Array<T | K> { |
实际应用
对接一下请求接口的数据
1 | const axios = { |
- 传入
Data
,返回Promise<Data>
,这样的话有约束,而且.then()
也有提示
泛型约束
泛型有时候太过灵活
1 | function add<T>(a: T, b: T) { |
加上约束
1 | function add<T extends number>(a: T, b: T) { |
1 | interface Len { |
1 | let obj = { |
keyof
是将对象的类型的key 作为一个联合类型
- 注意不是直接作用在对象身上,因为对象是对值 的引用,而
keyof
的作用对象是类型
可以这样使用
1 | type Key = keyof typeof obj; |
🙃keyof
的高级用法
1 | interface Data { |
这时候type B
会是
1 | type B = { |
其实使用Partial
也可以得到相同的结果
1 | type C = Partial<Data>; |
readonly
的话,就使用Readonly
装饰器
需要在配置文件中打开
experimentalDecorators
和emitDecoratorMetadata
装饰器有:
- 类装饰器
- 属性装饰器
- 参数装饰器
- 方法装饰器
- 装饰器工厂
类装饰器,在不破坏原有内容的情况下(或者说不知道原来有什么的情况下)增加内容
1 | const Base: ClassDecorator = (target) => { |
target
是一个构造函数
还有另一种写法是不用@Base
1 | class Http { |
❔❔ 如果要向@Base
传递参数呢
target
是默认存在的,好像事件监听的事件对象一样- 可以使用函数柯里化 事先参数传递
就是再套一层函数
- 外面一层函数接收
@Base
传过来的参数 - 里一层函数接收默认存在的参数
1 | const Base = () => { |
- 这一招叫做:装饰器工厂、函数柯里化、闭包
函数装饰器
1 | const Get = (url: string) => { |
- 有三个参数,装饰器自己的
啥也不用做,直接调用@Get()
传递个地址过去就行了
类型兼容
鸭子类型:两个类型有共通的地方
协变:子类型可以完全覆盖掉主类型(子类型可以多内容,但是不能少内容)
1 | // 主类型 |
逆变:一般在函数上出现(和协变是反过来的),类似于b = a
1 | let fna = (params: A) => {}; |
- (双向协变)配置文件开启
strictFunctionTypes
设为false
,第一个赋值可以成立 - 第二个之所以成立,因为赋值完了后
fnb
就是fna
utility
Record<Keys, Type>
组建一个对象类型,这个对象类型的 key 是 传入的 Keys ,对象的值是传入的 Type
1 | interface CatInfo { |
整理来源:📄 我的笔记、📺 bilibili小满zs
Typescript 快速手册 2.0