连夜爆肝只为将它送到你的面前,写给初级前端快速转TypeScript指南

Proud lion 前端 2021-08-30

一、为什么要用TypeScript

TypeScript可以让我们开发中避免一些类型或者一些不是我们预期希望的代码结果错误。xxx is not defined 我们都知道JavaScript错误是在运行中才抛出的,但是TypeScript错误直接是在编辑器里告知我们的,这极大的提升了开发效率,也不用花大量的时间去写单测,同时也避免了大量的时间排查Bug

二、TypeScript优缺点

优点

  • 一般我们在前后端联调时,都要去看接口文档上的字段类型,而TypeScript会自动帮我们识别当前的类型。节省了我们去看文档或者network时间。这叫做类型推导(待会我们会讲到)

  • 友好地在编辑器里提示错误,避免代码在运行时类型隐式转换踩坑。

缺点

  • 有一定的学习成本,TypeScript中有几种类型概念,interface接口class类enum枚举generics泛型等这些需要我们花时间学习。

  • 可能和一些插件库结合的不是很完美

三、TypeScript运行流程及JavaScript代码运行流程

1. JavaScript运行流程如下,依赖NodeJs环境和浏览器环境

  • JavaScript代码转换为JavaScript-AST
  • AST代码转换为字节码
  • 运算时计算字节码

2. TypeScript运行流程,以下操作均为TSC操作,三步执行完继续同上操作,让浏览器解析

  • TypeScript代码编译为 TypeScript-AST
  • 检查AST代码上类型检查
  • 类型检查后,编译为JavaScript代码
  • JavaScript代码转换为JavaScript-AST
  • AST代码转换为字节码
  • 运算时计算字节码

四、TypeScript和JavaScript区别

只有搞懂了二者的区别,我们才可以更好的理解TypeScript

类型系统特性JavaScriptTypeScript
类型是如何绑定?动态静态
是否存在类型隐式转换?
何时检查类型?运行时编译时
何时报告错误运行时编译时

类型绑定

JavaScript

JavaScript动态绑定类型,只有运行程序才能知道类型,在程序运行之前JavaScript对类型一无所知

TypeScript

TypeScript是在程序运行前(也就是编译时)就会知道当前是什么类型。当然如果该变量没有定义类型,那么TypeScript会自动类型推导出来。

类型转换

JavaScript

比如在JavaScript1 + true这样一个代码片段,JavaScript存在隐式转换,这时true会变成number类型number(true)和1相加。

TypeScript

TypeScript中,1+true这样的代码会在TypeScript中报错,提示number类型不能和boolean类型进行运算。

何时检查类型

JavaScript

JavaScript中只有在程序运行时才能检查类型。类型也会存在隐式转换,很坑。

TypeScript

TypeScript中,在编译时就会检查类型,如果和预期的类型不符合直接会在编辑器里报错、爆红

何时报告错误

JavaScript

JavaScript只有在程序执行时才能抛出异常,JavaScript存在隐式转换,等我们程序执行时才能真正的知道代码类型是否是预期的类型,代码是不是有效。

TypeScript

TypeScript中,当你在编辑器写代码时,如有错误则会直接抛出异常,极大得提高了效率,也是方便。

五、TypeScript总共围绕两种模式展开

显式注解类型

举个栗子

let name: string = "前端娱乐圈";

let age: number = 38;

let hobby: string[] = ["write code", "玩游戏"]
复制代码

显式注解类型就是,声明变量时定义上类型(官方话语就是声明时带上注解),让我们一看就明白,哦~,这个name是一个string类型。

推导类型

举个栗子

let name = "前端娱乐圈"; // 是一个string类型

let age = 38;  // 是一个number类型

let hobby = ["write code", "玩游戏"] // 是一个string数组类型
复制代码

推导类型就是去掉显示注解,系统自动会识别当前值是一个什么类型的。

六、安装TypeScript && 运行

typescript

全局安装typescript环境。

npm i -g typescript
复制代码

可是这只是安装了typescript,那我们怎么运行.ts文件呢,安装完typescript我们就可以执行tsc命令。

如:我们的文件叫做index.ts,直接在命令行执行tsc index.ts即可。然后就可以看到在目录下编译出来一个index.js,这就是tsc编译完的结果。

index.ts

const userName: string = "前端娱乐圈" 
复制代码

运行tsc index.ts,你可以看见在index.ts的同级下又生成一个index.js,如下就是编译的结果文件index.js

var userName = "前端娱乐圈"
复制代码

上面我们知道了运行tsc命令就可以编译生成一个文件,有的小伙伴觉得这样太麻烦了,每次运行只是编译出来一个文件还不是运行,还得用node index.js才可以运行。不急我们接着往下看

ts-node

我们来看一下这个插件ts-node,这个插件可以直接运行.ts文件,并且也不会编译出来.js文件。

npm i ts-node

// 运行 ts-node index.ts
复制代码

讲到这里我们了解了为什么要用TypeScript和它的优缺点以及它的运行工作方式

那么接下来步入TypeScript基础知识的海洋啦~,follow me。

感觉有帮助的小伙伴可以关注一下:前端娱乐圈 公众号,谢谢啦~,每天更新一篇小技巧

七、基础知识

1. 基础静态类型

TypeScript中基础类型跟我们JavScript中基础类型是一样的。只是有各别是Ts里面新出的。

1. number

const count: number = 18; // 显示注解一个number类型

const count1 = 18; // 不显示注解,ts会自动推导出来类型

复制代码

2. string

const str: string = "前端娱乐圈"; // 显示注解一个string类型

const str1 = "蛙人"; // 不显示注解,ts会自动推导出来类型
复制代码

3. boolean

const status: string = false; // 显示注解一个string类型

const status1 = true; // 不显示注解,ts会自动推导出来类型
复制代码

4. null

const value: null = null;

const value: null = undefined; // 这一点null类型可以赋值undefined跟在 js中是一样的,null == undefined
复制代码

5. undefined

const value: undefined = undefined;

const value: undefined = null; // 这一点null类型可以赋值undefined跟在 js中是一样的,null == undefined
复制代码

6. void

估计到这有一些小伙伴可能对void这个比较陌生,以为只有TypeScript才有的。其实不是哈,在我们JavaScript就已经存在void关键字啦,它的意思就是无效的,有的小伙伴可能看见过早些项目里面<a target="_blank" href="https://url.apipost.cn/url?javascript%3A+void%280%29" rel="external nofollow" >这是控制a标签的跳转默认行为。你不管怎么执行void方法它都是返回undefined

那么在我们TypeScriptvoid类型是什么呢。它也是代表无效的,一般只用在函数上,告诉别人这个函数没有返回值。

function fn(): void {} // 正确

function testFn(): void {
    return 1; // 报错,不接受返回值存在
}

function fn1(): void { return undefined} // 显示返回undefined类型,也是可以的

function fn2(): void { return null} // 显示返回null类型也可以,因为 null == undefined
复制代码

7. never

never一个永远不会有值的类型或者也可以说一个永远也执行不完的类型,代表用于不会有值,undefinednull也算做是值。一般这个类型就不会用到,也不用。大家知道这个类型就行。

const test: never = null; // 错误
const test1: never = undefined // 错误

function Person(): never { // 正确,因为死循环了,一直执行不完
    while(true) {}
}

function Person(): never { // 正确,因为递归,永远没有出口
    Person()
}

function Person(): never { // 正确 代码报错了,执行不下去
    throw new Error()
}
复制代码

8. any

any这个类型代表任何的任意的。希望大家在项目中,不要大片定义any类型。虽然它真的好使,那这样我们写TypeScript就没有任何意义了。

let value: any = ""; // 正确
value = null // 正确
value = {} // 正确
value = undefined // 正确
复制代码

9. unknown

unknown类型是我们TypeScript中第二个any类型,也是接受任意的类型的值。它的英文翻译过来就是未知的,我们来看一下栗子

let value: unknown = ""	 
value = 1;
value = "fdsfs"
value = null
value = {}
复制代码

那现在肯定有小伙伴疑惑,诶,那它unknown相当于是any类型,那二者的区别是什么。我们来看一下

let valueAny: any = "";
let valueUnknown: unknown = "";

valueAny = "蛙人";
valueUnknown = "前端娱乐圈"

let status: null = false;
status = valueAny; // 正确
status = valueUnknown // 报错,不能将unknown类型分配给null类型
复制代码

我们来看一下上面的,为什么any类型就能被赋值成功,而unknown类型不行呢,从它俩的意义来上看,还是有点区别的,any任何的,任意的、unknown未知的。所以你给unknown类型赋值任何类型都没关系,因为它本来就是未知类型嘛。但是你如果把它的unknown类型去被赋值一个null类型,这时人家null这边不干了,我不接受unknown类型。

说白了一句话,别人不接受unknown类型,而unknown类型接受别人,哈哈哈哈。

2. 对象静态类型

说起对象类型,我们肯定都能想到对象包含{}数组函数

1. object && {}

其实这俩意思一样,{}object表示非原始类型,也就是除numberstringbooleansymbolnullundefined之外的类型。

const list: object = {} // 空对象

const list1: object = null; // null对象

const list: object = [] // 数组对象

const list: {} = {}
list.name = 1 // 报错 不可更改里面的字段,但是可以读取
list.toString()
复制代码

2. 数组

const list: [] = []; // 定义一个数组类型

const list1: number[] = [1,2] // 定义一个数组,里面值必须是number

const list2: object[] = [null, {}, []] // 定义一个数组里面必须是对象类型的

const list3: Array<number> = [1,2,3] // 泛型定义数组必须是number类型,泛型我们待会讲到
复制代码

3. 类

// 类
class ClassPerson = {
    name: "前端娱乐圈"
}

const person: ClassPerson = new Person();
person.xxx = 123; // 这行代码报错,因为当前类中不存在该xxx属性
复制代码

4. 函数

// 函数
const fn: () => string = () => "前端娱乐圈" // 定义一个变量必须是函数类型的,返回值必须是string类型
复制代码

3. 函数类型注解

这里说一下函数显示注解和函数参数不会类型推导问题。

1. 函数返回类型为number

function fn(a, b): number {
    return a + b;
}
fn(1, 2)
复制代码

2. 函数void

显示注解为void类型,函数没有返回值。

function fn(): void {
    console.log(1)
}
复制代码

3. 函数不会自动类型推导

可以看到下面的函数类型,不会自动类型推导,我们实参虽然传入的1和2,但是形参方面是可以接受任意类型值的,所以系统也识别不出来你传递的什么,所以这里得需要我们显示定义注解类型。

function testFnQ(a, b) {
    return a + b
}
testFnQ(1,2)
复制代码

连夜爆肝只为将它送到你的面前,写给初级前端快速转TypeScript指南

我们来改造一下。

function testFnQ(a:number, b:number) {
    return a + b
}
testFnQ(1,2)
复制代码

连夜爆肝只为将它送到你的面前,写给初级前端快速转TypeScript指南

我们再来看一下参数对象显示注解类型,也是在:号后面赋值每个字段类型即可。

function testFnQ(obj : {num: number}) {
    return obj.num
}
testFnQ({num: 18})
复制代码

4. 元组Tuple

元组用于表示一个已知数组的数量和类型的数组,定义数组中每一个值的类型,一般不经常使用。

const arr: [string, number] = ["前端娱乐圈", 1]

const arr: [string, string] = ["前端娱乐圈", 1] // 报错
复制代码

5. 枚举Enum

Enum枚举类型,可以设置默认值,如果不设置则为索引。

enum color {
    RED,
    BLUE = "blue",
    GREEN = "green"
}

// color["RED"] 0
// color["BLUE"] blue

复制代码

像上面的colorRED没有设置值,那么它的值则为0,如果BLUE也不设置的话那么它的值则是1,它们这里是递增。如果设置值则是返回设置的值

注意这里还有一个问题,直接来上代码

通过上面学习我们知道了enum可以递增值,也可以设置默认值。但是有一点得注意一下,enum没有json对象那样灵活,enum不能在任意字段上设置默认值。

比如下面栗子,RED没有设置值,然后BLUE设置了默认值,但是GREEN又没有设置,这时这个GREEN会报错。因为你第二个BLUE设置完默认值,第三又不设置,这时代码都不知道该咋递增了,所以报错。还有一种方案就是你给BLUE可以设置一个数字值,这时第三个GREEN不设置也会跟着递增,因为都是number类型。

// 报错
enum color {
    RED,
    BLUE = "blue",
    GREEN
}

// good
enum color {
    RED,	   // 0
    BLUE = 4,  // 4
    GREEN      // 5
}
复制代码

比如enum枚举类型还可以反差,通过valuekey值。像我们json对象就是不支持这种写法的。

enum color {
    RED,	   // 0
    BLUE = 4,  // 4
    GREEN      // 5
}

console.log(color[4]) // BLUE
console.log(color[0]) // RED
复制代码

5. 接口Interface

接口interface是什么,接口interface就是方便我们定义一处代码,多处复用。接口里面也存在一些修饰符。下面我们来认识一下它们吧。

1. 接口怎么复用

比如在讲到这之前,我们不知道接口这东西,可能需要给对象定义一个类型的话,你可能会这样做。

const testObj: { name: string, age: number } = { name: "前端娱乐圈", age: 18 }

const testObj1: { name: string, age: number } = { name: "蛙人", age: 18 }
复制代码

我们用接口来改造一下。

interface Types {
    name: string, 
    age: number
}

const testObj: Types = { name: "前端娱乐圈", age: 18 }

const testObj1: Types = { name: "蛙人", age: 18 }
复制代码

可以看到使用interface关键字定义一个接口,然后赋值给这两个变量,实现复用。

2. readonly修饰符

readonly类型,只可读状态,不可更改。

interface Types {
    readonly name: string, 
    readonly age: number
}

const testObj: Types = { name: "前端娱乐圈", age: 18 }

const testObj1: Types = { name: "蛙人", age: 18 }

testObj.name = "张三" // 无法更改name属性,因为它是只读属性
testObj1.name = "李四" // 无法更改name属性,因为它是只读属性
复制代码

3. ?可选修饰符

可选修饰符以?定义,为什么需要可选修饰符呢,因为如果我们不写可选修饰符,那interface里面的属性都是必填的。

interface Types {
    readonly name: string, 
    readonly age: number,
    sex?: string
}

const testObj: Types = { name: "前端娱乐圈", age: 18}
复制代码

4. extends继承

我们的interface也是可以继承的,跟ES6Class类一样,使用extends关键字。

interface Types {
    readonly name: string, 
    readonly age: number,
    sex?: string
}

interface ChildrenType extends Types { // 这ChildrenType接口就已经继承了父级Types接口
    hobby: []
}
    
const testObj: ChildrenType = { name: "前端娱乐圈", age: 18, hobby: ["code", "羽毛球"] }
复制代码

5. propName扩展

interface里面这个功能就很强大,它可以写入不在interface里面的属性。

interface Types {
    readonly name: string, 
    readonly age: number,
    sex?: string,
}

const testObj: Types = { name: "前端娱乐圈", age: 19, hobby: [] } 
复制代码

上面这个testObj这行代码会爆红,因为hobby属性不存在interface接口中,那么我们不存在的接口中的,还不让人家写了?。这时候可以使用自定义就是上面的propName

interface Types {
    readonly name: string, 
    readonly age: number,
    sex?: string,
    [propName: string]: any // propName字段必须是 string类型 or number类型。 值是any类型,也就是任意的
}

const testObj: Types = { name: "前端娱乐圈", age: 19, hobby: [] } 
复制代码

在运行上面代码,就可以看到不爆红了~

6. Type

我们再来看一下Type,这个是声明类型别名使的,别名类型只能定义是:基础静态类型对象静态类型元组联合类型

注意:type别名不可以定义interface

type Types = string;

type TypeUnite = string | number

const name: typeUnite = "前端娱乐圈"
const age: typeUnite = 18
复制代码

1. 那么type类型别名和interface接口有什么区别呢

1. type不支持interface声明
type Types = number
type Types = string // 报错, 类型别名type不允许出现重复名字

interface Types1 {
    name: string
}

interface Types1 {
    age: number
}

// interface接口可以出现重复类型名称,如果重复出现则是,合并起来也就是变成 { name:string, age: number }
复制代码

第一个Types类型别名type不允许出现重复名字,interface接口可以出现重复类型名称,如果重复出现则是,合并起来也就是变 { name:string, age: number }

再来看一下interface另一种情况

interface Types1 {
    name: string
}

interface Types1 {
    name: number
}
复制代码

可以看到上面两个同名称的interface接口,里面的属性也是同名称,但是类型不一样。这第二个的Types1就会爆红,提示:后续声明的接口,必须跟前面声明的同名属性类型必须保持一致,把后续声明的name它类型换成string即可。

2. type支持表达式 interface不支持
const count: number = 123
type testType = typeof count

const count: number = 123

interface testType {
    [name: typeof count]: any // 报错
}

复制代码

可以看到上面type支持表达式,而interface不支持

3. type 支持类型映射,interface不支持
type keys = "name" | "age"  
type KeysObj = {
    [propName in keys]: string
}

const PersonObj: KeysObj = { // 正常运行
    name: "蛙人",
    age: "18"
} 

interface testType {
    [propName in keys]: string // 报错
}
复制代码

7. 联合类型

联合类型|表示,说白了就是满足其中的一个类型就可以。

const statusTest: string | number = "前端娱乐圈"

const flag: boolean | number = true
复制代码

再来看一下栗子。我们用函数参数使用联合类型看看会发生什么

function testStatusFn(params: number | string) {
    console.log(params.toFixed()) // 报错
}

testStatusFn(1)
复制代码

上面我们说过了,函数参数类型不能类型自动推导,更何况现在用上联合类型,系统更懵逼了,不能识别当前实参的类型。所以访问当前类型上的方法报错。

接下来带大家看一些类型保护,听着挺高级,其实这些大家都见过。别忘了记得关注:前端娱乐圈 公众号哦,嘻嘻

1. typeof

function testStatusFn(params: number | string) {
    console.log(params.toFixed()) // 报错
}
testStatusFn(1)
复制代码

改造后

// 正常
function testStatusFn(params: string | number) {
    if (typeof params == "string") {
        console.log(params.split)
    }

    if (typeof params == "number") {
        console.log(params.toFixed)
    }
}

testStatusFn(1)
复制代码

2. in

// 报错
interface frontEnd {
    name: string
}

interface backEnd {
    age: string
}

function testStatusFn(params: frontEnd | backEnd) {
    console.log(params.name)
}

testStatusFn({name: "蛙人"})
复制代码

改造后

// 正常
function testStatusFn(params: frontEnd | backEnd) {
    if ("name" in params) {
        console.log(params.name)
    }

    if ("age" in params) {
        console.log(params.age)
    }
}

testStatusFn({name: "蛙人"})
复制代码

3. as 断言

// 报错
interface frontEnd {
    name: string
}

interface backEnd {
    age: string
}

function testStatusFn(params: frontEnd | backEnd) {
    console.log(params.name)
}

testStatusFn({name: "蛙人"})
复制代码

改造后

// 正常
function testStatusFn(params: frontEnd | backEnd) {
    if ("name" in params) {
        const res = (params as frontEnd).name
        console.log(res)
    }
    
    
    if ("age" in params) {
        const res = (params as backEnd).age
        console.log(res)
    }
}

testStatusFn({age: 118})
复制代码

8. 交叉类型

交叉类型就是跟联合类型相反,它用&表示,交叉类型就是两个类型必须存在。这里还用上面的联合类型的栗子来看下。

interface frontEnd {
    name: string
}

interface backEnd {
    age: number
}

function testStatusFn(params: frontEnd & backEnd) {}

testStatusFn({age: 118, name: "前端娱乐圈"})
复制代码

这里我们可以看到实参必须传入两个**接口(interface)**全部的属性值才可以。联合类型是传入其中类型就可以。

注意:我们的接口interface出现同名属性

interface frontEnd {
    name: string
}

interface backEnd {
    name: number
}

function testStatusFn(params: frontEnd & backEnd) {
    console.log(params)
}

testStatusFn({name: "前端"})
复制代码

上面我们两个接口类型中都出现了同名属性,但是类型不一样,这时类型就会变为never

9. 泛型

泛型是TypeScript中最难理解的了,这里我尽量用通俗易懂的方式讲明白。

function test(a: string | number, b: string | number) {
    console.log(a, b)
}
test(1, "前端娱乐圈")
复制代码

比如上面栗子,函数参数注解类型定义stringnumber,调用函数实参传入也没什么问题,但是有个需求,就是实参我们必须传入同样的类型(传入两个number类型)。虽然上面这种联合类型也可以实现,但是如果我们要在加一个boolean类型,那么联合类型还得在追加一个boolean,那这样代码太冗余了。

这时就需要用到泛型了,泛型是专门针对不确定的类型使用,并且灵活。泛型的使用大部分都是使用<T>,当然也可以随便使用,如:<Test><Custom>都可以。

function test<T>(a: T, b: T) {
    console.log(a, b)
}
test<number>(1, "前端娱乐圈") // 调用后面跟着尖括号这就是泛型的类型,这时报错,因为在调用的使用类型是number,所以只能传入相同类型的

test<boolean>(true, false) 

test<string>("前端娱乐圈", "蛙人")
复制代码

上面这使用泛型就解决了我们刚才说的传入同一个类型参数问题,但是泛型也可以使用不同的参数,可以把调用类型定义为<any>

function test<T>(a: T, b: T) {
    console.log(a, b)
}

test<any>(1, "前端娱乐圈")
复制代码

但是上面这种又有一种问题,它可以传入对象,但是如果我们只希望传入number类型和string类型。那么我们泛型也给我们提供了**约束类型。泛型使用extends进行了类型约束**,只能选择stringnumber类型。

function test<T extends number | string, Y extends number | string>(a: T, b: Y) {
    console.log(a, b)
}

test<number, string>(18, "前端娱乐圈")

test<string, number>("前端娱乐圈", 18)
复制代码

这时,传入泛型时使用逗号分隔,来定义每一个类型希望是什么。记住,只有我们不确定的类型,可以使用泛型。

10. 模块

TypeScript也支持importexport这里大多数小伙伴都知道,这里都不多讲啦。

// 导入

import xxx, { xxx } from "./xxx"

// 导出

export default {}

export const name = "前端娱乐圈"
复制代码

如有不明白的小伙伴,可以看我以前文章 聊聊什么是CommonJs和Es Module及它们的区别

11. Class类

以下这三个修饰符是在TypeScript类中才能使用,在JavaScript类中是不支持的。

连夜爆肝只为将它送到你的面前,写给初级前端快速转TypeScript指南

1. public

public的公共属性,就是不管在的内部还是外部,都可以访问该属性方法。默认定义的属性方法都是public

class Person {
	name = "前端娱乐圈";
	public age = 18;
}
const res = new Person();
console.log(res.name, res.age) // 前端娱乐圈 18
复制代码

上面可以看到打印结果都能显示出来,name属性没有定义public公共属性,所以里面定义的属性方法默认都是public定义。

2. private

private的私有属性,只有在当前里面才能访问,当前就是{}里面区域内。在{}外面是不能访问private定义的属性方法

class Person {
	private name = "前端娱乐圈";
	private age = 18;
}
const res = new Person();
console.log(res.name, res.age) // 这俩行会爆红,当前属性为私有属性,只能在类内部访问

class Scholl extends Person {
    getData() {
        return this.username + "," + this.age
    }
}
const temp = new Scholl()
console.log(temp.getData()) // 爆红~,虽然继承了Person类,但是private定义是只能在当前类访问,子类也不能访问。
复制代码

3. protected

protected的保护属性,只有在当前类子类可以访问。也就是说用protected属性定义的子类也可以访问。

class Person {
    protected username = "前端娱乐圈";
    protected age = 18;
}
const res = new Person();
console.log(res.name, res.age) // 这俩行会爆红,当前属性为私有属性,只能在类内部访问

class Scholl extends Person {
    getData() {
        return this.username + "," + this.age
    }
}
const temp = new Scholl()
console.log(temp.getData()) // 前端娱乐圈,18。可以正常访问父类的属性
复制代码

4. implements

implements关键字只能在class中使用,顾名思义,实现一个新的类,从父级或者从接口实现所有的属性和方法,如果在PersonAll类里面不写进去接口里面已有的属性和方法则会报错。

interface frontEnd {
    name: string,
    fn: () => void
}

class PersonAll implements frontEnd {
    name: "前端娱乐圈";
    
    fn() {
        
    }
}
复制代码

5. 抽象类

抽象类使用abstract关键字定义。abstract抽象方法不能实例化,如果,抽象类里面方法是抽象的,那么本身的类也必须是抽象的,抽象方法不能写函数体。父类里面有抽象方法,那么子类也必须要重新该方法。

// 抽象类
abstract class Boss {
    name = "秦";
    call() {} // 抽象方法不能写函数体
}

class A extends Boss {
    call() {
        console.log(this.name);
        console.log("A")
    }
}

class B extends Boss {
    call() {
         console.log("B")
    }
}

new A().call()
复制代码

该抽象类使用场景,比如A需求或者B需求正好需要一个公共属性,然后本身还有一些自己的逻辑,就可以使用抽象类,抽象类只能在TypeScript中使用。

12. 命名空间namespace

我们学到现在可以看到,不知道小伙伴们发现没有,项目中文件是不是不能有重复的变量(不管你是不是一样的文件还是其它文件),否则就直接爆红了。命名空间一个最明确的目的就是解决重名问题。

命名空间使用namespace关键字来定义,来看栗子吧。

index.ts

namespace SomeNameSpaceName { 
    const q = {}

    export interface obj {
        name: string
    }
}
复制代码

上面这样,就定义好了一个命名空间,可以看到变量q没有写export关键字,这证明它是内部的变量,就算别的.ts文件引用它,它也不会暴露出去。而interface这个obj接口是可以被全局访问的。

我们在别的页面访问当前命名空间

1. reference引入

/// <reference path="./index.ts" />
namespace SomeNameSpaceName { 
	export class person implements obj {
		name: "前端娱乐圈"
	}
}
复制代码

2. import

export interface valueData {
     name: string
}
复制代码
import { valueData } from "./xxx.ts"
复制代码

这时使用命名空间之后完全可以解决不同文件重名爆红问题。

13. tsConfig.json

这个tsconfig文件,是我们编译ts文件,如何将ts文件编译成我们的js文件。tsc -init这个命令会生成该文件出来哈。执行完该命令,我们可以看到根目录下会生成一个tsconfig.json文件,里面有一堆属性。

那么我们怎么将ts文件编译成js文件呢,直接执行tsc命令可以将根目录下所有的.ts文件全部编译成.js文件输出到项目下。

更多配置文档,请参考官网

{
    // include: ["*.ts"] // 执行目录下所有的ts文件转换成js文件
    // include: ["index.ts"] // 只将项目下index.ts文件转换为js文件
    // files: ["index.ts"] // 跟include一样,只执行当前数组值里面的文件,当前files必须写相对路径
    // exclude: ["index.ts"] // exclude就是除了index.ts不执行,其它都执行
    
    compilerOptions: {
        removeComments: true, // 去掉编译完js文件的注释
        outDir: "./build", // 最终输出的js文件目录
        rootDir: "./src", // ts入口文件查找
    }
}
复制代码

八、实用类型

最后来说一下实用类型,TypeScript标准库自带了一些实用类型。这些实用类都是方便接口Interface使用。这里只列举几个常用的,更多实用类型官网

1. Exclude

从一个类型中排除另一个类型,只能是联合类型,从TypesTest类型中排除UtilityLast类型。

适用于:并集类型

interface UtilityFirst {
    name: string
}

interface UtilityLast {
    age: number
}

type TypesTest = UtilityFirst | UtilityLast;

const ObjJson: Exclude<TypesTest, UtilityLast> = {
    name: "前端娱乐圈"
}
复制代码

2. Extract

Extract正好跟上面那个相反,这是选择某一个可赋值的联合类型,从TypesTest类型中只选择UtilityLast类型。

适用于:并集类型

interface UtilityFirst {
    name: string
}

interface UtilityLast {
    age: number
}

type TypesTest = UtilityFirst | UtilityLast;

const ObjJson: Extract<TypesTest, UtilityLast> = {
    age: 1
}
复制代码

3. Readonly

把数组或对象的所有属性值转换为只读的。这里只演示一下对象栗子,数组同样的写法。

适用于:对象、数组

interface UtilityFirst {
    name: string
}

const ObjJson: Readonly<UtilityFirst> = {
    name: "前端娱乐圈"
}
ObjJson.name = "蛙人" // 报错 只读状态
复制代码

4. Partial

把对象的所有属性设置为选的。我们知道interface只要不设置?修饰符,那么对象都是必选的。这个实用类可以将属性全部转换为可选的。

适用于:对象

interface UtilityFirst {
    name: string
}

const ObjJson: Partial<UtilityFirst> = {
    
}
复制代码

5. Pick

Pick选择对象类型中的部分key值,提取出来。第一个参数目标值,第二个参数联合key

适用于:对象

interface UtilityFirst {
    name: string,
    age: number,
    hobby: []
}

const ObjJson: Pick<UtilityFirst, "name" | "age"> = {
    name: "前端娱乐圈",
    age: 18
}
复制代码

6. Omit

Omit选择对象类型中的部分key值,过滤掉。第一个参数目标值,第二个参数联合key

适用于:对象

interface UtilityFirst {
    name: string,
    age: number,
    hobby: string[]
}

const ObjJson: Pick<UtilityFirst, "name" | "age"> = {
    hobby: ["code", "羽毛球"]
}
复制代码

7. Required

Required把对象所有可选属性转换成必选属性。

适用于:对象

interface UtilityFirst {
    name?: string,
    age?: number,
    hobby?: string[]
}

const ObjJson: Required<UtilityFirst> = {
    name: "蛙人",
    age: 18,
    hobby: ["code"]
}
复制代码

8. Record

创建一个对象结果集,第一个参数则是key值,第二个参数则是value值。规定我们只能创建这里面字段值。

适用于:对象

type IndexList = 0 | 1 | 2

const ObjJson: Record<IndexList, "前端娱乐圈"> = {
    0: "前端娱乐圈",
    1: "前端娱乐圈",
    2: "前端娱乐圈"
}
复制代码

九、闲聊

给在北京通州租房小伙伴们提个醒,最近被这狗B小房地产公司坑了,租房到期故意不退押金,大家在通州找房的小伙伴们注意一下,千万别碰见这家狗中介公司,这家公司注册一堆公司名,北京珺奇地产北京珺邺地产等,打着二十一世纪旗号故意骗人。你们肯定问我为啥找这么小公司租房,特马的这里还得吐槽一下这个某居客App,我第一次用这个App租房,没想到这里面都是一堆类似于这种的小中介公司,真的很恶心。租房还得找大公司,靠谱安心。

感谢

谢谢你读完本篇文章,希望对你能有所帮助,如有问题欢迎各位指正。

我是蛙人,如果觉得写得可以的话,请点个赞吧❤。

感兴趣的小伙伴可以加入 [ 前端娱乐圈交流群 ] 欢迎大家一起来交流讨论

写作不易,「点赞」+「在看」+「转发」 谢谢支持❤

下篇更新TypeScript实战,欢迎关注

公众号:前端娱乐圈。转载请联系我开白

往期推荐

《这些操作删除console.log代码,你都知道吗》

《什么场景下使用Render函数,如何配置JSX》

《分享15个Webpack实用的插件!!!》

《手把手教你写一个Vue组件发布到npm且可外链引入使用》

《分享12个Webpack中常用的Loader》

《聊聊什么是CommonJs和Es Module及它们的区别》

《这些工作中用到的JavaScript小技巧你都知道吗?》

《【建议收藏】分享一些工作中常用的Git命令及特殊问题场景怎么解决》

参考资料

作者:蛙人

链接:https://juejin.cn/post/7000610903615864869

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

中文版Postman,rap文档管理

评论