一、前言
学完一个东西,总要总结一下才能巩固,好记性不如烂笔头。本文想对TypeScript进行详细的基础知识总结,若有错误之处还请指正。另外本文中的示例可直接在 playground 上运行。
使用的TypeScript版本: v4.8.4
二、优缺点
1、优点
提前发现错误:在编写代码的时候就能发现大部分的错误,避免代码到运行出现bug。更愉快的写代码:结合编辑器(vscode真是好东西)的语法提示功能、智能补全功能、类型定义等。可读性和可维护性:能够及时的检测错误,程序员更容易写出更健壮、更清晰的代码,在大型项目中不同的程序员维护更加简单。
2、缺点
增加学习成本
三、类型
1️⃣ 基础类型
boolean string number enum undefined null Array tuple any unknown void never
1、boolean、string、number
const amIRich: boolean = true
const myName: string = "Tony"
const myAge: number = 18
console.log(typeof amIRich)
console.log(typeof myName)
console.log(typeof myAge)
2、enum
简单使用 第一个值默认是0,剩下的会按顺序增加,类似数组的下标。
enum Animal {
DOG,
CAT,
MOUSE
}
const animal: Animal = Animal.DOG
console.log(animal) // 打印 0
设置初始值
// number
enum Animal {
DOG = 1,
CAT, // 自动为2
MOUSE // 自动为3
}
const cat: Animal = Animal.CAT
console.log(cat) // 打印 2
// string 必须手动设置 不能自动推断
enum City {
SH = "shanghai",
BJ = "beijing",
SZ = "shenzhen"
}
const shanghai: City = City.SH
console.log(shanghai) // 打印 shanghai
const 修饰的常量枚举 用const 修饰的常量枚举在ts转换成js的时候,不会保留枚举的特性。
// const 修饰的enum
const enum City {
SH = "shanghai",
BJ = "beijing",
SZ = "shenzhen"
}
const shanghai: City = City.SH
console.log(shanghai) // 打印 shanghai
Playground 中翻译的js如下:
"use strict";
const shanghai = "shanghai" /* City.SH */;
console.log(shanghai); // 打印 shanghai
3、undefined、null
默认情况下 可以把 undefined 和 null 赋值给其他类型
// 未开启 "strictNullChecks":true
let u: undefined = undefined
let n: null = null
// 可以复制给其他类型
const myName: string = u
const willRich: boolean = n
// 还可以相互赋值
u = n
配置文件tsconfig.json中 配置 “strictNullChecks”:true
// 开启了 "strictNullChecks":true
let u: undefined = undefined
let n: null = null
// const name: string = u // 会报错
// u = n // 不能相互赋值
// undefined 可以赋值给 void
// null 不能复制给void
const a: void = u
// const b: void = n // 会报错
4、Array、tuple
元组(tuple) 规定了已知长度和类型的数组,只能按定义的长度和对应的类型来赋值。
// 普通的数组
const arr: string[] = ['dog', 'cat', 'mouse']
//等价于
const arr1: Array
// tuple元组是 更加严格的数组
const zhangsan: [string, number, string] = ['张三', 18, '男']
5、any、unknown
any: 可以说是ts开的“后台”,可以跳过类型检查,所以任何类型都可以复制给any。
// any -> 任何类型赋值给any
const MyName: any = '法外狂徒张三'
const myMoney: any = 100000000
const isAnimal: any = true
// any -> any赋值给其他任何类型
const newName: string = MyName
const dog: boolean = isAnimal
unknown: 和any一样,所有类型都可以赋值给unknown。区别在于unknown只能赋值给unknown和any。
// any -> 任何类型赋值给any
const MyName: unknown = '法外狂徒张三'
const myMoney: unknown = 100000000
const isAnimal: unknown = true
// any -> any赋值给其他类型
// const newName: string = MyName // 报错
const money: unknown = 100000000 // 正确
const dog: unknown = isAnimal // 正确
6、void、never
void常用在函数上,一般函数有返回值,有可能是string、number、boolean等等,当函数没有返回值的时候,则是void类型,表明函数没有返回值。
function fn(): void {
console.log('今天你内卷了吗?')
console.log('没有返回值')
}
fn()
never表示永远也不会执行完的函数。比如说在函数内部有死循环,这个函数永远也不会执行结束。或者函数中有抛出异常,执行的时候被异常中断了,这个函数也就没有执行到底。
// 死循环
function fn(): never {
while(true){}
}
// 抛出异常
function errFn(): never {
throw new Error()
}
2️⃣ 对象类型
函数 对象 数组 类
1、函数
函数是用的非常多的类型了。
普通使用
const fn: () => void = () => {
console.log('中国红')
}
// () => void 表示 fn的类型是一个没有参数 返回值是void(就是没有返回值)的函数
// () => { console.log('中国红') } 是fn的值
默认参数
function fn(age: number = 18):void {
console.log('您的年龄是: ' + age)
}
fn()
可选参数
function sum(a: number, b: number = 1, c?: number) {
if(c) {
return a + b + c
}
return a + b
}
console.log(sum(1,2)) // 3
console.log(sum(1,2,3))// 6
剩余参数
必须是一个数组类型。
function sum(...numbers: number[]) {
let res = 0
for(let i = 0; i < numbers.length; i++) {
res += numbers[i]
}
return res
}
console.log(sum(1,2)) // 3
console.log(sum(1,2,3))// 6
2、对象、数组、类
对象
一般用 接口:interface 来实现类似的功能,所以此处不在深入。
let a: object = {}
let b: object = { name: '张三' }
let c: {}
c = { name: '张三' }
console.log(c)
数组
// 普通的数组
const arr: string[] = ['dog', 'cat', 'mouse']
//等价于
const arr1: Array
类
简单的了解一下,后面做详细的介绍。
class People {
name: string
age: number
constructor(name:string, age: number) {
this.name = name
this.age = age
}
speak() {
console.log('我的名字: ' + this.name)
console.log('我的年龄: ' + this.age)
}
}
const zhangsan = new People('张三', 18)
zhangsan.speak()
3️⃣ 接口 interface
interface 用的也非常广泛,灵活性也非常强。也常用于对「对象的形状(Shape)」进行描述。一般首字母大写。定义接口类型的变量需要实现里面全部的普通的属性或者方法。
简单的例子
interface Person {
name: string
age: number
speak: () => void // 定义函数类型
}
const lisi: Person = {
name: '李四',
age: 18,
speak() {
console.log('我会说话')
}
}
可选属性、只读属性 可选属性允许不需要必须实现接口里面的属性或者方法,也就是说该属性或者方法可以实现也可实现。用 *?*表示。 只读属性就是只允许读取,不允许修改的属性。
interface Person {
name?: string
readonly age: number
}
const lisi: Person = {
// name: '李四', // 可选 可以定义也可以不定义
age: 18
}
// readonly 修饰 只读
// lisi.age = 19 // 报错
鸭式辨型法
就是说只要有鸭子的特征他就是鸭子,只要传入的对象包含了接口的属性或者方法(鸭子的特征)那么可以认定传入的参数实现了这个接口,可以绕开额外的属性检查。
interface nameValue {
name: string;
}
function getNmae(nameObj: nameValue) {
console.log(nameObj.name);
}
let lisi = { age: 18, name: "我是李四" };
getNmae(lisi); // 通过
// getNmae({ age: 18, name: "我是李四" }); // 报错
4️⃣ 类型操作
1、联合类型
某个变量可能有两个或者两个以上的类型,用 | 来表示。
let a: string | number = 1
a = 2 // 正确
a = 'lisi' // 正确
2、交叉类型
变量的类型,是两个类型的结合,相同key中的类型要一样。用 & 来表示。
interface a {
name: number
age: number
}
interface b {
name: string
city: string
}
// c 包含 a 和 b 属性
// 如果a和b中的name属性类型不一样的话,则不能使用 &
let c: (a & b) = {
name: '李四',
age: 18,
city: '北京'
}
3、类型推断
没有明确指定类型的时候,ts会尝试推断其类型。推断不出来的时候会赋予any类型。
const myName = "lisi" // 推断出 myName 是 string类型
4、类型断言
有时候自己更加清楚知道变量的类型,所以可以手动指定类型,使用 as 来指定类型.
// 方式一 使用 <类型>值
const Myname = "abcdefg"
const count1 = (Myname as string).length
5、类型别名
使用type来表示,相当于给类型重新起一个名字, 可以用在基础的类型如 string 、number、boolean和interface定义的类型等等。
interface a {
name: number
say: () => void
}
type str = string // str 是 string类型的另一个名字
type num = number
type b = a // b 是 a类型的别名
6、类型守卫
当传进来的值的类型不止一种的时候,就需要对值进一步的判断,确保值的类型在可控的范围内。
in 关键字
可以判断 某个 key 在不在其中。
interface a {
name: number
age: number
}
interface b {
name: string
city: string
}
function fn(param: a | b) {
if('age' in param)
console.log(param.age)
if('city' in param)
console.log(param.city)
}
typeof 关键字
和js中的typeof类似,可以判断 bool、string、number等等的类型
function fn(x: number|string, y: number|string):string|number {
if(typeof x === 'number' && typeof y === 'number') {
return x + y
}
return `${x}${y}`
}
instanceof 关键字
只能用于 类 的类型。
问:typeof 和 instanceof 的区别?
function getDate(date: Date | string) {
if(date instanceof Date) {
// 如果date是Date的实例
return date.getDate()
}
return date
}
四、class
类在其他强类型的语言很常见、对部分前端的小伙伴来说获取有些陌生。类是一个思想的转变。
1、成员变量/方法的权限
public、private、protected、readyonly
public默认值,表示公开的,外部和子类都能访问到。
private私有的,表示只能本类中访问,外部和子类都不能访问。
protected外部不能访问,子类可以访问到。
readyonly表示只读,不能被修改。
2、static 修饰符
普通的变量/方法,在类被实例化的时候创建。static 修饰的变量/属性是挂载在类本身,实例化之后不能通过this访问到static修饰的成员。
创建一个单例
// 静态属性 static
// ****** 创建 class 单例的方法 ******
// static 可以创建在类本身,而不是new出来的实例上
class Demo {
private static instance: Demo
public name: string
// 构造函数私有化,就相当于不能实例化
private constructor(name: string) {
this.name = name
}
public static getInstance() {
// 第一次创建的时候就会把instance挂在到Demo本身上去,第二次就不会再创建实例
if(!this.instance) {
this.instance = new Demo("hello")
}
return this.instance
}
}
const demo1 = Demo.getInstance()
const demo2 = Demo.getInstance()
// 单例
console.log(demo1.name) // hello
console.log(demo2.name) // hello
3、抽象类
抽象类用 abstract修饰,类似于interface的功能。
// 抽象类 用 abstract 修饰
// 类中用 abstract 修饰之后变成抽象方法
// 抽象类只能被继承
abstract class Gemo {
constructor(public width: number, height: number ) {
}
abstract getArea(): number
public setWidth(width:number) {
this.width = width
}
}
class Circle extends Gemo {
public getArea(): number {
return this.width
}
}
// interface 可以继承 类似于抽象类相同的功能 将相同的提取出来
五、泛型
1️⃣ 泛型的作用
泛型是用来解决传入类型不固定的问题,比如说传入的类型可能是string、number…或者方法返回的类型有可能是number、string等等,那么就可以约定一个泛型来表示该类型,一般用T表示。除了T之外,常见泛型变量代表的意思:
K(Key):表示对象中的键类型;V(Value):表示对象中的值类型;E(Element):表示元素类型。
2️⃣ 基本使用
在类中使用泛型
/*
类泛型: 类型不确定的时候,指定类型
*/
interface Item {
name: string
}
class DataManager
// 传入的是 包含 T 的数组
constructor(private data: T[]) {}
public getData(index: number) {
// 根据下包获取数组对应的值
return this.data[index]
}
}
const dataArr = new DataManager([{name: "lisi"}])
console.log(dataArr.getData(0))
在接口中使用泛型
interface KeyValue
key: K;
value: V;
}
3️⃣ 泛型工具类型
泛型表示任意类型,当在某处调用了该值下的一个属性,但是该类型是不一定有该属性,所以会发生不可预期的错误,如:
function fn
// param 不知道是什么具体的解构
// console.log(param.length)
// 所以需要增加一些工具来判断参数是什么结构
}
为了更好的约束泛型,使用一些工具来增强泛型的可读性。
1、typeof
可以获取变量或者属性的类型,也可以获取函数的类型。
interface Person {
name: string
age: number
getName: () => string
}
const lisi: Person = {name: '李四', age: 18, getName: () => '李四'}
type Teacher = typeof lisi // 类型 Teacher = 类型 Person
type Fn = typeof lisi.getName // 此处 fn是函数类型:() => string
const getNickname: Fn = () => {
return '常山赵子龙'
}
2、keyof
获取类型的所有键,返回的是键的联合类型。
interface Person {
name: string
age: number
getName: () => string
}
type key1 = keyof Person // 'name' | 'age' | 'getName'
type key2 = keyof Person[] // 数组的键 如:'length' | 'toString' | 'pop' | 'push' | 'concat' | 'join'
type key3 = keyof string // string类型的键 如:number | 'toString' | 'charAt' | 'charCodeAt' | 'concat'...
当键是索引类型的时候
键是string类型keyof返回的类型是 string | number
键是number类型keyof返回的类型是 number
// 当是索引类型的时候
type key4 = keyof { [k: string]: boolean } // string | number
type key5 = keyof { [k: number]: boolean } // number
如何使用keyof?
假设我们有以下函数:
function prop(obj, key) {
return obj[key];
}
函数中obj和key是不确定的,所以是不能确定是否可以执行 obj[key] 这个操作。 进一步优化:
function prop(obj: object, key: string) {
return (obj as any)[key];
}
很显然加上 any 虽然可以解决燃眉之急,但这任然不是一个好的方案,还是不能确定是否可以执行 obj[key] 这个操作。用keyof可以解决这个问题:
function prop
return obj[key];
}
不管obj中有什么属性,key总能找到对应键值。
interface Person {
name: string
age: number
}
const lisi: Person = {
name: '李四',
age: 18
}
function prop
return obj[key];
}
const myName = prop(lisi, 'name') // myName: string
// const myName = prop(lisi, 'nickname') // 传入不存在的键值,则报错
3、in
用来遍历枚举类型。
enum Words {
A,
B,
C
}
type AliasWords = {
[w in Words]: string
}
// AliasWords 的类型等价为
// type AliasWords = {
// 0: string;
// 1: string;
// 2: string;
// }
4、infer
在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用。
type ReturnType
...args: any[]
) => infer R ? R : any;
infer R 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。
5、extends
extends 就是类型继承,和值的继承了类似,用在类型中增加类型的约束。
interface Todo {
id: number
}
function add
return todo
}
const task = {
id: 1,
add: () => {
console.log('增加一项任务')
}
}
// 传入的 task 必须包含 id 这个字段
const myTask = add(task)
myTask.add()
4️⃣ 内置工具类型
TS 内置了一些类型的操作符,方便我们快速的操作类型。首字母都是大写。
1、Partial
Partial 可以把类型的所有属性变成可选。
interface User {
id: number
name?: string
status: number
super: boolean
}
type Coder = Partial
// Coder 的类型变成
// type Coder = {
// id?: number | undefined;
// name?: string | undefined;
// status?: number | undefined;
// super?: boolean | undefined;
// }
const lisi: Coder = {}
注意的是,Partial 只能处理一层的数据,当有嵌套的二层数据不会进行处理。
2、Required
和 Partial 相反,Required是将所有属性变成必选。
3、Readonly
Readonly是将所有属性变成只读。
4、Pick
定义
type Pick
可见是需要传入两个参数。如以下例子:
interface User {
id: number
readonly name?: string
status: number
super?: boolean
}
type Coder = Pick
// Coder 的类型变成
// type Coder = {
// id: number;
// }
// 必传 id 属性
const lisi: Coder = {id: 1}
5、Record
用来批量赋予某个类型。
interface User {
id: number
readonly name?: string
status: number
super?: boolean
}
type Leader = "zhangsan" | "lisi" | "wangwu"
type Coder = Record
// Coder 的类型变成
// type Coder = {
// zhangsan: User;
// lisi: User;
// wangwu: User;
// }
6、ReturnType
获取函数返回值的类型。
type getMoney = () => number
// 上面的函数返回值是 number
const money: ReturnType
7、Exclude
将 某个类型 从类型集中剔除。
type ManyTypes = boolean | string | number
type newTypes = Exclude
// 剩下类型
// type newTypes = string | number
8、Extract
和 Exclude 相反,Extract从类型集中提取某个类型
type ManyTypes = boolean | string | number
type newTypes = Extract
// 剩下类型
// type newTypes = boolean
9、Omit
Omit 剔除某个属性,剩余的组建新的类型。
interface User {
id: number
readonly name?: string
status: number
super?: boolean
}
type Coder = Omit
// Omit 剔除 status 属性 剩余以下属性
// type Coder = {
// id: number;
// readonly name?: string | undefined;
// super?: boolean | undefined;
// }
10、NonNullable
NonNullable 的作用是用来过滤类型中的 null 及 undefined 类型。
type Name = string | undefined | null
type UserName = NonNullable
11、Parameters
Parameters 的作用是用于获得函数的参数类型组成的元组类型。
type Param = Parameters<(name: string, age: number) => void>
// 类型 Param 为: type Param = [name: string, age: number]
六、tsconfig.json介绍
tsconfig.json 是 TypeScript 项目的配置文件。使用 tsc xxx.ts 命令可以通过配置文件编译出相应的js代码。
1、tsconfig.json 重要字段
files - 设置要编译的文件的名称;include - 设置需要进行编译的文件,支持路径模式匹配;exclude - 设置无需进行编译的文件,支持路径模式匹配;compilerOptions - 设置与编译流程相关的选项。
2、compilerOptions 选项
{
"compilerOptions": {
/* 基本选项 */
"target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在编译中的库文件
"allowJs": true, // 允许编译 javascript 文件
"checkJs": true, // 报告 javascript 文件中的错误
"jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相应的 '.d.ts' 文件
"sourceMap": true, // 生成相应的 '.map' 文件
"outFile": "./", // 将输出文件合并为一个文件
"outDir": "./", // 指定输出目录
"rootDir": "./", // 用来控制输出目录结构 --outDir.
"removeComments": true, // 删除编译后的所有的注释
"noEmit": true, // 不生成输出文件
"importHelpers": true, // 从 tslib 导入辅助工具函数
"isolatedModules": true, // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).
/* 严格的类型检查选项 */
"strict": true, // 启用所有严格类型检查选项
"noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错
"strictNullChecks": true, // 启用严格的 null 检查
"noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误
"alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
/* 额外的检查 */
"noUnusedLocals": true, // 有未使用的变量时,抛出错误
"noUnusedParameters": true, // 有未使用的参数时,抛出错误
"noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误
"noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)
/* 模块解析选项 */
"moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用于解析非相对模块名称的基目录
"paths": {}, // 模块名到基于 baseUrl 的路径映射的列表
"rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容
"typeRoots": [], // 包含类型声明的文件列表
"types": [], // 需要包含的类型声明文件名列表
"allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。
/* Source Map Options */
"sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
"inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
/* 其他选项 */
"experimentalDecorators": true, // 启用装饰器
"emitDecoratorMetadata": true // 为装饰器提供元数据的支持
}
}
七、最后
我是菜鸡,一步一步往上爬。
相关阅读
发表评论