这是一份林一一同学学习 typescript4.0 的笔记,希望能对前端er有帮助。
介绍Typescript
Typescript 是什么?
( 图片来源网络,侵权删除)
- Typescript 是 JavaScript 的超集,两者是所属关系。
- Typescript 是 JavaScript 的增强,包含 JavaScript 的最新特性,非常适合创建大型项目
- Typescript 是静态语言与动态语言 JavaScript 不同,TS 是和 JS 都是弱类型语言
- Typescript 也是前端的趋势,各大著名的前端框架都使用了 TS 重构,如 Vue, React 等
下载安装及使用
- 本地环境需要先下载 node vscode,随后打开 vscode 终端,使用
npm install -g typescript
即可 tsc -v
查看版本号,这份教程是最新的 typescript 4.0- 建议安装 TSlint 插件规范代码
- 编写 TS 文件代码,使用
tsc xx.ts
命令运行 ts 文件,运行 ts 文件后会生成相应的 js 文件,这个 js 文件时 tsc 将 xx.ts 代码编译成 xx.js的代码
动态语言和静态语言的差别
- 静态语言在编写代码的时候就能发现潜在的错误.
- 静态语言更容易读懂代码,像上面的data参数静态语言能直接读出里面的属性 x, y,但是动态语言参数 data 显然不能直接读出里面包含什么属性。
// TS
function tsFunc (data: {x: number, y: number}) {
console.log('demo ts')
return Math.sqrt(data.x ** 2 + data.y ** 2)
}
// tsFunc() // 没有传入参数,这里的代码 vscode 会提示错误,这就是静态语言在编写代码的时候就可以知道有错误。
tsFunc({x: 3, y: 4}) //需要将参数代码一起写入。
// 再比如传入参数的个数,ts 能直接检测,但是生成的 js 文件不能检测
function get(param) {
return param
}
get('hello')
get('hello', 1) // error: 应有 1 个参数,但获得 2 个
- 编写代码时静态语言能识别到可能使用到的属性等,但动态语言不一定能提示的正确。
// 静态类型和动态类型的差别,静态类型在编写代码时就可以发现错误像C++,Java等,动态类型的语言则需要代码运行时才可以知道错误,像JavaScript,python。
// js-code
function jsFunc (data) {
return Math.sqrt(x ** 2 + y ** 2)
}
jsFunc() // 没有传入参数,但这里的代码 vscode不会提示错误,但实际运行会发生报错。
二. 静态类型
静态类型
就像前面看到的那样静态类型可以是基础类型 number string null undefined symbol boolean void enum 还可以是对象类型 Object,Array, class, function,还可以是自定义类型 interface 或任何类型 any 等详情 typescript官网
- 定义为相应类型后可以直接使用对应类型的方法或属性如 number,vscode 直接提示
. 基础类型 number string null undefined symbol boolean any void never。。。
- number 类型
const num: number = 123
- string 类型
const Name: string = 'LinYY'
- boolean 类型
const boolean: Boolean = true
- null 类型 。null 类型不可以赋值给 undefined 类型和 联合类型(后面介绍)
let n: null = null
- undefined 类型
let u: undefined = undefined
undefined 类型。可以作用到可选类型,因为可选的类型默认会有一个undefined 类型
interface E {
b: number
c?: number
}
let e: E = {b: 12, c: 12}
e = {b: 23, c: undefined}
- any 类型。已经定义变量的类型不能再修改,否则报错。
注意 any 类型,any 类型定义后可以修改为其他的类型
// any 类型可以修改成其他任何类型,TS 不对 any 类型作类型检测
let not: any
not = 2
not = '2'
not = true
// 处理不确定的数组类型 any 比较合适。
let listArr: any[] = ['1', 2, true]
- void 类型 和 any 类型相反,表示没有任何类型
void 类型 通常作用在函数中代表没有返回值,虽然也可以作为其他变量的类型,但只能赋值成 undefined。换一个方向想函数总是有返回值的,如果不是一个确定的值那么就是 undefined 值,所以 void 其实是属于 undefined 的,所以一个变量类型是 void 时,值只能 undefined 值。但是 不能将类型“void”分配给类型“undefined”详情看例子
// void 空类型,一般用于函数,
function noReturn(): void {
console.log('no value return')
}
function fn(): void {
// Error
return 3;
}
function fn5(): void {
}
let un: undefined = fn5(); // Error 不能将类型“void”分配给类型“undefined”
let voidValue: void = undefined
let voidValue2: void = null // 不能将类型“null”分配给类型“void”
- never 一个特殊类型。简单的说如果函数是一个永远不会执行完的函数,返回值就是 never 类型,像函数
errorFunc
,abs
。
// never 类型,不会执行结束的函数类型
function errorFunc(): never {
throw new Error()
console.log('never') // 抛出错误后 这段代码不打印。
}
function abs(): never {
while (true) {
}
console.log('never') // 上面的代码永远是true 这段代码不打印。
}
- 对象类型 object type。object {},array [], class {}, function
let person: {
name: string,
age: number
} = {
name: 'LinYY',
age: 12
}
// 或 (不推荐写法)
let personB:{name: string} & {age: number} = {
name: 'LinYY',
age: 12
}
- 数组类型 也是对象类型,下面声明number型数组只能写入数字来初始化,写入字符串将会报错。
const list: number[] = [12, 23, 34]
//等同于,下面的数组泛型,泛型是什么之后会讲,先留一个印象。
const listA: Array<number> = [1, 2, 3]
// const listB: number[] = ['12', 23, 34]
- class类 类型
class Person {}
const LinYY: Person = new Person()
- function 函数类型, 下面的函数类型要求返回值是 number 数字类型,写成其他类型如 string 会报错。
const getNumber: () => number = () => {
// return 'LinYY' 报错
return 123
}
// 要求返回值是string 字符类型
const getString: () => string = () => {
return 'LinYY'
// return 123
}
- interface 自定义类型,也就是接口
interface Point {
x: number,
y: number
}
const point: Point = {
x: 2,
y: 4
}
- 多类型。变量的类型可以有多个,比如可以是 number 或 string类型
// 变量的类型可以有多个,比如可以是number或string类型。
let temp: number | string = 23
temp = '23'
- type alias 类型别名,类型别名不是 TS 的基本数据类型,类型别名常用于提取公共类型,下面 interface 接口会详细介绍
type User = { name: string, age: number }
let male: User = {name: 'LinYY', age: 18}
let famale: User = {name: 'nana', age: 18}
小 tip
注释小技巧tip: 使用 /** */
可以给类型添加更友好的提示
// 注释
/**
* this is good Per
*/
interface Per {
name: string
}
const p: Per ={
name: 'LinYY'
}
在 typescript 里面 name 是一个预留关键字,不能直接当变量来用
三.类型注解和类型推断
type annotation 类型注解。
- 直接声明的类型,告诉TS变量是什么类型。比如这里的 count 是 number 类型
// type annotation 类型注解。
let count: number
count = 23
type inference 类型推断。
- 没有直接声明类型,TS 会尝试去分析变量类型,如这里的 countB,推断是 number 类型。(此处加图说明)
let countB = 23
- 但是声明的变量没有直接在一行赋值 TS 将默认变量为 any 类型,如这里的变量 countC,鼠标箭头移动到 countC 上方就可以看到类型。正确写法是加上类型注解。
let countC // any 类型
countC = 233
TS 并不能所有的类型都能推断出来,那么什么时候使用类型注解呢?具体情况需要具体分析
- 一般简单的变量声明可以不写类型注解。如这里的
num1 num2 sum
。TS 能直接判断。 - 一般函数的参数需要类型注解,返回值可以不用写类型注解,TS 能自动判断
- 已经有未确定的 any 类型,需要加类型注解,如 total 显示为 any,原因是类型 a b 不确定。
let num1 = 1
let num2 = 2
let sum = num1 + num2 // TS 推断出 sum 是 number 类型
let obj = {
name: 'LinYY',
age: 18
}
// obj.name = 23 // TS 推断出来的类型 同样不能再修改
// 需要类型注解
function getSum(a, b) {
return a + b
}
const total = getSum(2, 3)
四.TS 函数
TS 定义函数的方法和 JS 基本一样,不同的是 TS 可以要求有无返回值。
- TS 返回值的类型可以是 number string 等类型 也可以是 void 类型即没有返回值,也可以是联合类型。
// 要求返回值是 number 数字类型,下面两种写法等价。
const fooFunc = (a: number, b: number): number => {
return a + b
}
//: (a: number, b: number) => number' 这里的具体意义是函数参数 a, b 类型是 number 型,返回值是 number 型,后面的 = 是跟函数的具体实现
const foo1: (a: number, b: number) => number = (a, b) => {
return a + b
}
// 返回值是 void 空类型
function sum2(a: number, b: number): void {
console.log(a + b)
}
// 联合类型
function sum2(a: number, b: string): number | string {
return a || b
}
TS 函数的类型解构正确用法
- 加入类型注解能避免意外的bug,具体看实例
// 避免意外的 bug,当传入的 person 中没有 name 属性时下面的代码会报错,TS 能规避这个问题给出报错提示
const getNameA = (person: {}) => {
console.log(person.name) // ==> undefined
}
const getName1 = (person: { name: string }) => {
console.log(person.name)
}
- 解构一个函数的参数是 对象 的方式是在后面跟着一个对象类型注解,像函数
add()
- 一般函数的参数需要类型注解,返回值可以不用写类型注解,TS 能自动判断
// 不加入对象类型的注解{a: number, b: number},返回值则不能保证是预期的类型.
function add({ a, b }: { a: number, b: number }): number {
const c = a + b
return c
}
const sum1 = add({ a: 1, b: 2 })
// 参数为对象的正确注解方式
function getNumberA({ a }: { a: number }) {
return a
}
const totalA = getNumberA({ a: 1 })
// 如这里不能保证放回值是number类型,因为a, b类型为any类型。
function add2({ a, b }): number {
const c = a + b
return c
}
TS 中的 this,TS 是 JS 的超集,this 的指向法则都一样
- 理解 this 指向 对我们使用 JS TS 特别重要,记住最关键的一点:this 永远指向最后调用它的那个对象
- 解决 es5 的 this 使用的坑可以使用 箭头函数,箭头函数在创建时就保存了最近上下文的 this 的值。
this 指向最后调用 this 的对象
this 指向 示例一
let name = 'foo'
let f = {
name: "Lin",
a: function () {
console.log(this.name) // ==> Lin
}
}
f.a() // f.a() == window.f.a()
// 为什么是 Lin?因为 对象 f 调用了函数 a(),再调用了 this。上面 window 调用了 f 对象,但是 f 才是最后调用 this 的对象
再看示例二
var age = 18
function foo() {
let age = 20
console.log(this.age) // ==>18
}
foo() // == window.foo()
//为什么是18,不是20呢?上面 foo() 函数是 window 对象调用了,所以 this 指向 window,那么 使用的 age 也就是window下的 age
再看示例三
let name = 'LinYY'
let a = {
name: 'lin',
c: function () {
return function () {
console.log(this.name)
}
}
}
let b = a.c()
b() // window.b()
// 为什么这里 打印的是 LinYY 呢?和示例二类似,b() 最后是被 window 对象调用了,所以还是 “this 指向最后调用 this 的对象”
箭头函数解决 this指向
// 问:如果就是想要使用对象 a 上下文呢?那么就可以使用 箭头函数。保存最近的上下文的this,也就是这里的对象 a
let name = 'LinYY'
let a = {
name: 'lin',
c: function () {
return () => {
console.log(this.name)
}
}
}
let b = a.c()
b()
五.数组和元组
数组的类型注解
数组的类型声明分为两种,一: 类型[], 二: Array<类型>,两种写法等价具体看下面示例
- 同变量的类型注解,数组也可以具备多个类型,number,string等。
// 单类型注解数组 [ ],number[] 表示是数字类型的数组,其他同理
const numberArr: number[] = [1, 2, 3] // 等同于 const numberArr: Array<number> = [1, 2, 3]
const stringArr: string[] = ['1', '2', '3'] // // 等同于 const stringArr: Array<string> = ['1', '2', '3']
const undefinedArr: undefined[] = [undefined]
// 多类型注解数组 [ ]
const arr: (number | string)[] = [1, 2, '2']
// 对象数组
const objectArr: { name: string, age: 18 }[] = [{
name: 'LinYY',
age: 18
}]
- 对于复杂的对象数组,可以采用类型别名 type alias。
// 采用类型别名 type alias
type User = { name: string, age: number }
const objectArr1: User[] = [{
name: 'LinYY',
age: 18
}]
- 传入的数据结构和定义的 type alias 或 class 一致,也是可以的。
// 数据结构一致 TS 不会报错
class Teacher {
name: string = ''
age: number = 0
}
// 这里是一个 Teacher 类,那么每一个元素都应该是 Teacher 的实例,但是由于下面的对象数据结构和 Teacher类一致,所以 TS 没有报错。
const objectArr2: Teacher[] = [
new Teacher(),
{
name: 'LinYY',
age: 18
}]
// 反例:因为 Teacher 类中没有 other 属性,TS 会提示 “other”不在类型“Teacher”中
const objectArr3: Teacher[] = [
new Teacher(),
{
name: 'LinYY',
age: 18,
other: 0
}]
元组 tuple 是一种特殊的数组 (TS 新增)
- 简单的说元组是每个元素都有固定的类型且有限数目的数组。
- 元组解决了普通数组不能规定每一项元素类型的问题。
- 元组的元素同样有对象的类型操作方法
- 在 3.1 及之前版本中,超出规定个数的元素称作越界元素,但是只要越界元素的类型是定义的类型中的一种即可。比如我们定义的类型有两种:
string 和 number
,越界的元素是string 或 number
类型,属于联合类型,但是在 3.1 之后的版本,去掉了这个越界元素是联合类型的子类型即可的条件,要求元组赋值必须类型和个数都对应。越界元素直接时undefined
类型
// tuple 元组的定义
let arrT: [number, string, number]
arrT = [18, 'LinYY', 3]
// 元素操作方法
arrT[0].toExponential(1)
arrT[1].split('i')
arrT[2] = 4
// 类型不对应就会报错
arrT = [18, 'LinYY', '1']
// 越界元素,直接报错
arrT[3] = '12' // 不能将类型“"12"”分配给类型“undefined”
type tupleArr = [number, string, number]
const arrTB: tupleArr = [18, 'LinYY', 3]
const attTC: tupleArr[] =[
[18, 'LinYY', 3],
[1, 'LinYY', 23],
[2, 'LinYY', 13]
]
// 普通数组不能约束每一项元素的类型,下面元素的类型就不可以约束
let list: (number | string)[] = ['LinYY', 18]
let listB: (number | string)[] = [18, 'LinYY']
- 使用场景:批量的来获取参数,并且每一个参数的类型还不一样
/**巧用元组 */
function getParams(...arry:[string, number, boolean]){
const str: string = arry[0];
const num: number = arry[1];
const b: boolean = arry[2];
}
六.interface 接口
- 简单点理解 interface 接口其实是一个类对象,类对象里面有属性或方法,同样通过 '.' 元字符操控属性和方法,属性和方法又有相应的类型
string,number
等 - 能定义对象类型和函数类型,一般使用在类型中,接口类型一般大写首字母
// 一个简单实例示例说明 interface 是一个类对象
interface PersonA {
firstName: string,
lastName: string
}
function greeter(person: PersonA) {
return person.firstName + person.lastName
}
let userA = {
firstName: 'lin',
lastName: 'YY'
}
greeter(userA)
- 还能定义以后可能用到的属性,在属性后面加上
'?'
,类似函数可选参数,如示例
// 可能用到的属性,在属性后面加上'?',
interface Person {
name: string,
age: number,
age1?: number, // age1 是接口可能用到的属性。
readonly ID: number
}
const person: Person = {
name: 'LinYY',
age: 18,
// age1: 0,
ID: 101
}
- 只读属性 能对属性定义进行只读操作
readonly
,在对应属性面前加上 readonly 就能限定只读操作。只读属性被初始化后的值不能在被修改。
// 只读操作 readonly 不传入 age1 也可以通过校验
const getName = (person: Person) => { // Person 是上面的 interface 接口
console.log(person.age)
// console.log(person.age1) ==> 0
console.log(person.ID)
// person.ID = 200 // 报错 ID只能读取,不能修改==> error TS2540: Cannot assign to 'ID' because it is a read-only property
}
getName(person)
- interface 中还能定义方法,跟着的类型表示返回值类型。
// interface 中定义方法
interface Search{
(a: number, b: number): boolean
}
let search: Search
search = function(a: number, b: number): boolean {
return a >= b
}
search(2, 3)
// 或
interface Action {
name: string,
age: number,
say(): string
}
const applySay = (action: Action) => {
console.log(action.say())
}
const action = {
name: 'LinYY',
age: 18,
say() {
return 'hello TS'
}
}
applySay(action)
// 下面定义了一个 say 类型的接口 接受一个 string 的参数,返回 string 类型的字符串
interface Say {
(word: string): string
}
const foo: Say = (word: string) => {
return word
}
foo('hello TS') // ==> 'hello TS'
- interface 间还能相互嵌套
/**interface 间还能相互嵌套 */
interface A {
name: string,
age: number
}
interface B {
/** person is interface a */
person: A
}
- 也可以被类 class 通过
implements
使用,实现(implements)是面向对象中的一个重要概念,简单点理解就是实现接口 interface 中属性和方法,详情参考官网 implements
// implements 和 extend 不同 extend 是继承父类,implement 是实现接口 interface 而且可以使用多个接口,用逗号隔开。
// class A extends B implements C,D,E
interface Person {
name: string,
age: number,
age1?: number, // age1 是接口可能用到的属性。
readonly ID: number
}
class test implements Person {
name = 'LinYY'
age = 18
ID = 301
}
- interface 还可以被其他接口继承 extends
// 被其他接口继承 extends
interface Music {
click: boolean
}
interface Sentence {
color: string
}
interface Classic extends Music, Sentence {
time: number
}
let classic = {} as Classic
classic.click = false
classic.color = 'white'
classic.time = 220
// 或
interface PersonB {
name: string,
age: number,
age1?: number, // age1 是接口可能用到的属性。
readonly ID: number
}
interface Teach extends PersonB {
action(): string
}
const teach: Teach = {
name: 'LinYY',
age: 28,
ID: 501,
action() {
return '222'
}
}
// 或使用 '<类型>' 表示类型
const teachA = <Teach>{
name: 'LinYY',
age: 28,
ID: 501,
action() {
return '222'
}
}
- interface 可以继承类吗?可以!
// interface 继承 class 示例
class Animal {
fly: any
}
interface Dog extends Animal {
run(): void
}
注意点
- interface 接口中不强制规定后来加上的属性,只要传入参数满足 interface 接口已有的属性,也能通过 如示例二,age,sex 属性不在接口 Person 中也能通过校验
// 示例二
function printName(obj: { name: string }) {
console.log(obj.name)
}
let myObj = { name: 'LinYY', age: 18 }
printName(myObj)
// 或
const getAge = (person1: Person) => {
console.log(person1.age)
}
const per = {
name: 'LinYY',
age: 18,
ID: 201,
sex: 'male' // 不在 Person 接口内,也可以通过校验
}
getAge(per)
- 但是以字面量形式传入不在接口中的属性,TS 会强校验导致报错。
// 以字面量的形式传入,TS 会强校验导致校验不通过。
getAge({
name: 'LinYY',
age: 18,
ID: 201,
sex: 'male' // ==> 'sex' does not exist in type 'Person'
})
interface 实际上在编译成 JS 后并没有相应的代码,其实 interface 就是 TS 来约束代码代码规范的。
怎么有效解决以后开发的过程中可能会加入的属性呢?
- 可以直接在 interface 接口中加入以后可能会用到的属性如,string 类型 [propName: string]: any,如示例三
// 示例三 [propName: string]: any
interface User {
name: string,
age: number,
[propName: string]: any // 后期可能用到的 string 类型的属性
}
const getSex = (user: User) => {
console.log(user.age)
}
const user = {
name: 'LinYY',
age: 18,
ID: 201,
sex: 'male' // 不在 User 接口内,但也能通过
}
getSex(user)
// 以字面量的形式传入也可以。
getSex({
name: 'LinYY',
age: 18,
ID: 201,
sex: 'male'
})
interface 和 type alias 关系
两者用法上面没有太大的区别,同样都可以扩展,只是语法不同,type 使用交叉类型 &
。而且两者相互之间可以继承。但是 interface的应用场景更加的广,能够使用interface就不用type
// interface 和 type alias
/** ifc is interface */
interface Ifc {
name: string,
age: number
}
/** T is type alias */
type T = {
name: string,
age: number
}
/* interface 和 type alias 扩展示例 */
interface IfcName {
name: string
}
interface IfcAge extends IfcName {
age: number
}
type TName = {
name: string
}
/** TAge 继承了 TName 的 name 属性*/
type TAge = TName & {
age: number
}
const tAge : TAge = {
name: 'LinYY',
age: 18
}
/* interface 和 type alias 相互继承示例 */
/** interface extends type alias */
interface IfcAge extends TName {
age: number
}
/** type & interface alias */
type TypeName = IfcAge & {
name: string
}
七.class 类
TS 中类的继承和定义和 JS 基本一致
- 同样也能重写父类的属性
- 子类中有构造器 就必须要调用 super()。
// TS 中类的定义
class PersonA {
name: string
constructor(msg) {
this.name = msg
}
getName() {
return this.name
}
}
const personA = new PersonA('LinYY')
console.log(personA.getName()) // ==> LinYY
// TS 类的继承
class Student extends Person {
say() {
return this.name
}
getName() {
return 'LinYY' // 重写父类 getName方法
}
}
const student = new Student()
console.log(student.say()) // ==> LinYY
TS 类中的访问类型,构造器和 JS 也基本一致
访问类型 private,public,protected
- 当类的属性不写访问类型时,默认是 public。public 在类的内外都可以访问。
- private,protected都只能在类内使用,另外 protected 可以在继承的子类中使用。
constructor 构造器,在类实例初始化时执行。
- TS 中运用
constructor
示例如下。 - 子类继承父类,子类中使用
constructor
,就需要调用super()
函数。
// constructor 示例
class PersonB {
public name: string
constructor( name: string) {
this.name = name
}
}
// 简化写法,推荐。
// class PersonB {
// constructor(public name: string) {
// }
// }
const personB = new PersonB('LinYY')
class TeacherA extends PersonB {
constructor(public age: number ) {
super('LinYY') // 初始化父类的 name
}
}
const teacher = new TeacherA(18)
- class 的基本概念和用法可以访问 阮老师的 ES6入门教程
- TS 静态属性 static 和 JS 一样。在类的内外都是通过类名来直接访问,且不能被继承,在静态方法中使用另一个静态方法可以直接调用 this。非静态方法不行**
// static
class GetAge {
static age = 18
static printAge() {
console.log(GetAge.age)
}
static setAge(msg: number) {
this.age = msg
this.printAge()
}
}
- 和 interface 一样 class 也可以当作接口使用
// 将 class 当作接口使用
class A {
x: number
y: number
constructor(x: number, y: number) {
this.x = x
this.y = y
}
}
interface B extends A {
z: number
}
let printA: A = {x: 2, y: 3}
TS 类中的静态属性,取值函数(getter)和存值函数(setter),对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
Getter 的使用
- 定义 get 的属性表面上是一个函数,在调用时不需要加上 '()',这是 get 的写法。
- 访问类中的私有属性可以通过 get 属性,写入一个‘方法’返回私有属性
Setter 的使用
- 设置私有属性可以通过 set 属性,同样也是写入一个‘方法’,简单的说也就是赋值
class Person {
constructor(private _name: string){} // 私有属性一般加下划线 '_'
get name() {
return this._name
}
set name(rename: string) {
this._name = rename
}
}
// get
const person = new Person('LinYY')
console.log(person.name) // ==> LinYY 调用 get 下面的 name 属性
// set
person.name = 'LinYYB' // 调用 set 下面的 name 属性同时赋值
console.log(person.name) // ==> LinYYB
修饰器 readonly 也可以设置类的只读属性
// 装饰器`readonly`用来装饰“类”的`name`属性。
class PersonB {
readonly name: string
constructor(private _name: string){
this.name = _name
} // 私有属性一般加下划线 '_'
}
抽象类 abstract 目的是将有很多共性的方法或属性抽离出来
- 抽象类一般拿来做公共的类。
- 抽象类里面可以写入抽象方法,抽象方法不能具体的实现,如
getApiData()
。 - 抽象类只能被继承不能被实例化。
- 子类继承抽象类后,抽象类的抽象方法必须在子类里面实现,否则报错。
- 抽象类和 interface 有相似,interface 同样也可以抽离一些公用方法和被继承。
import axios from 'axios'
// 抽象类 abstract
abstract class Classic {
name: string
async submit() {
return 'LinYY'
}
abstract getApiData() // 一个抽象方法
}
// const classic = new Classic() // error: 无法创建抽象类的实例,只能被继承
class Music extends Classic {
async getApiData() {
return await axios.get('api1URL').then( res => {
console.log(res)
}).catch( e => {
console.log(e)
})
}
}
class Book extends Classic {
async getApiData() {
return await axios.get('api2URL').then( res => {
console.log(res)
}).catch( e => {
console.log(e)
})
}
}
class Sentence extends Classic {
async getApiData() {
return await axios.get('api3URL').then( res => {
console.log(res)
}).catch( e => {
console.log(e)
})
}
}
八.TS 交叉类型和联合类型和类型保护
交叉类型
- 交叉类型通过
'&'
将两个或多个类型合并到一起 - 交叉类型可以获取合并类型的所有属性
// 交叉类型
interface Colors {
red: string
}
interface Rectangle {
height: number
width: number
area: () => number
}
// param 参数可以访问类型 Colors 和 Rectangle 所有属性
function getArea(param: Colors & Rectangle) {
param.height = 2
param.width = 3
param.red = 'red'
param.area = (): number => {
return param.height * param.width
}
}
联合类型
- 通过
'|'
运算符将两个类型组合在一起 - 联合类型可以直接提示出共有属性,但不能提示出私有属性
// 联合类型
let bar: string | number = 12
bar = '12'
interface Bird {
fly: Boolean;
sing: () => {}
}
interface Dog {
fly: Boolean;
dark: () => {}
}
// animal 参数可以是 Bird 或 Dog,语法提示可以直接提示出共有属性 fly,但是不能直接提示出 sing 和 dark。
function trainAnimal(animal: Bird | Dog) {
animal.fly
// animal.dark() 这里直接报错,因为不能确保 animal 包含 dark 方法。
}
类型保护
- 类型断言:让编辑器采用开发者的类型声明
- 类型断言,通过
as
,告诉 TS 当前的类型
//类型保护——类型断言 as
function trainAnimal1(animal: Bird | Dog) {
if (animal.fly) {
(animal as Bird).sing() //直接告诉 TS 这里 animal 是 Bird 类型
// 或下面的一种写法
// (<Bird>animal).sing()
} else {
(animal as Dog).dark()
}
}
- 通过
in
判断含有的私有属性
// in 判断
function trainAnimal2(animal: Bird | Dog) {
if ("sing" in animal) { // 判断 animal中是否含有私有属性 sing
animal.sing()
} else {
animal.dark()
}
}
- typeof,instanceof 类型于上面两种都可以作类型保护
// typeof 类型保护
function trainAnimal3(paramA: number | string, paramB: number | String) {
if (typeof paramA === "string" && typeof paramB === "string") {
return paramA + paramB
}
return
}
- null 的类型保护 可以通过
'!'
操作符
// null 类型保护
function fn(params: string | null) {
// params.length // 对象可能为 "null"
return params!.length // 将参数的可能类型 null 类型排除
}
fn('12121')
fn(null)
九.Enum 枚举类型
- 概念:枚举是什么? 枚举是一种数据类型,是对 JavaScript 类型的补充,其实也是一个对象
- 用途:一般是对相似的类型赋予一个友好的名字,如颜色有很多种通称为 Color 等
- 使用:同样类似于 interface 接口,enum 也是通过元字符 '.' 操控属性
- 每一个枚举类型都有一个下标,默认从 0 开始,下标值可以用等号赋值修改
- 从哪里改变枚举下标值,则按那里开始下标加一,如 ONLINE = 3,则OTHERS将会打印出4,OFFLINE 不变还是0
- 引用 enum 的下标,同样也可以打印出下标对应的值。
// 枚举是一种数据类型
enum Color {
Red,
Blue,
Black
}
let color: Color
// 类型也只能是枚举 Color 类型
color = Color.Red
color = Color.Blue
console.log('color', color) // ==> 打印出下标 1
// 枚举一般首字母大写
enum Status {
// OFFLINE = 1,
OFFLINE,
ONLINE,
OTHERS
}
// 对比常用的 JS 代码
// const Status = {
// OFFLINE : 0,
// ONLINE: 1,
// OTHERS: 2
// }
function getStatus(status: Number) {
if(status === Status.ONLINE) {
return 'online'
}else if (status === Status.OFFLINE) {
return 'offline'
}else if(status === Status.OTHERS) {
return 'others'
}
return 'error'
}
const result = getStatus(Status.OFFLINE)
console.log(result)
// 下面代码直接打印出 enum 的下标值
console.log(Status.OFFLINE)
console.log(Status.ONLINE)
console.log(Status.OTHERS)
// 打印下标对应的属性
console.log(Status[0])
console.log(Status[1])
console.log(Status[2])
原理:为什么枚举可以通过下标的方式索引 key 和 反索引 value呢?
- 通过 tsc 编译后的 js 文件可以看到 enum 其实一个对象,如 Color[Color["Red"] = 0] = "Red" ; 通过 "Red"可以查看下标 0,通过下标 0 也可以查看 "Red"
// 经过 tsc 编译后的 js 文件
var Color;
(function (Color) {
Color[Color["Red"] = 0] = "Red";
Color[Color["Blue"] = 1] = "Blue";
Color[Color["Black"] = 2] = "Black";
})(Color || (Color = {}));
十.TS 泛型
泛型概念:泛指的类型不具体针对某一特定的类型。
- 使用
'<类型>'
表示泛型,类型可以是任意变量
//泛型简单示例
function foo<T>(a, b, c) {
return a || b || c
}
// 或
function foo<U>(a, b, c) {
return a || b || c
}
function id<T>(a: T, b: T, c: T): T {
return a || b || c
}
// 不针对某一特定类型,可以是 number,string,Boolean,interface等类型,且使用的类型必须一致。
interface Type {
a: number
b: number
c: number
}
let t: Type = {a: 2, b: 3, c: 4}
id(1,2,3)
id('1', '2', '3')
id(false, true, false)
id(t.a, t.b, t.c)
// 接口泛型 推荐写法,直接使用泛型参数 T 代表指定类型
interface Uni<T> {
a: T
b: T
c: T
}
let uni: Uni<number> = {a: 2, b:3, c:4}
id(uni.a, uni.b, uni.c)
函数泛型 Generics,泛指的类型和普通类型用法基本一致
- TS 的泛型是 TS 比较高级的用法,关于更多的 TS 泛型可以参考这篇 你不知道的 TypeScript 泛型
- 泛型起到的作用有点类似待指的中间类型,具体看例子
function add(a: number | string, b: number | string) {
return `${a}${b}`
}
// 如果要求输入的参数 a, b只能是同一个类型的参数该怎么实现?下面几种写法都不能实现
add(1, 2)
add('1', 1)
add(1, '1')
// 下面引用函数泛型实现,<T> 就是函数 add 要泛指的类型,后面的参数 a,b都要是这个类型。
function add<T>(a: T, b: T){
return `${a}${b}`
}
//让输入的类型是 number 或 string,即<T>代表类型 Number或String
add<number>(1, 1)
add<string>('1', '2')
add<number>(1, '1') //提示报错 "1"的类型不是 number 型
add<string>('1', 2) //提示报错 2 的类型不是 string 型
- 泛型同样可以继承 interface。
// 泛型同样可以继承 interface。
interface LengthPro {
age: number | null
moreAge: number
}
function arg<T extends LengthPro>(params: T) {
return params.age || params.moreAge
}
arg({age: 18, moreAge: 20})
arg({age: null, moreAge: 20})
- 还可以对函数泛型进行约束
//将函数的泛型指定为 ABC,参数的泛型是数组类型ABC,返回值也是数组类型ABC。
function func<ABC>(a:ABC[]): ABC[]{
return a
}
// 或
function func1<ABC>(a:Array<ABC>): Array<ABC> {
return a
}
func<number>([123])
// 错误的输入
func<number>(123) // 类型“123”的参数不能赋给类型“Number[]”的参数
- 泛型中可以写入多个泛型,对函数的参数有不同的类型约束
// 多泛型约束
function moreT<T, Y>(a:T, b: Y) {
return `${a}${b}`
}
moreT<number, string>(1, '2')
// K extends keyof T,让泛型 K 继承 T 的 keyof 的属性
function getObjVal<T, K extends keyof T>(obj:T, key: K) {
return obj[key]
}
let o = {a: 1, b: 3, c: 4}
getObjVal(o, 'a')
getObjVal(o, 'd') // 'd' 不是 对象 o 下面的 keyof 属性。
类的泛型
// 类泛型的普通写法
class GenericType<T> {
numberVal: T
constructor(numberVal: T) {
this.numberVal = numberVal;
}
add(x: T, y: T) {
}
}
let numberType = new GenericType<number>(2)
numberType.numberVal = 1
numberType.add(2, 8)
let stringType = new GenericType<string>('a')
stringType.numberVal = 'b'
stringType.add('a', 'b')
// 或
class GetItem<T>{
constructor(private data: T[]) { }
getName(index: number) {
return this.data[index]
}
}
const getItem = new GetItem(['LinYY'])
const res = getItem.getName(0)
console.log(res)
- 泛型可以被继承,继承后必须全部含有继承对象的所有属性
// 继承示例 extends
interface personA {
name: string,
age: number
}
class GetItemSecond<T extends personA>{
constructor(private data: T[]) { }
getName(index: number) {
return this.data[index].name
}
}
const getItemSecond = new GetItemSecond([
{
name: 'LinYY',
age: 18 // 这里不传入 age 时会飘红,必须传入所有属性
}
])
const res1 = getItem.getName(0)
console.log(res1)
- 也可以约束泛型的类型范围,number 或 string
// 约束泛型的范围
function getData<T extends number | string>(param: T) {
return param
}
getData(1)
getData('1')
- 多泛型的约束用逗号分隔,泛型之间也通过继承 extends 来相互约束
// 多泛型约束
function moreT<T, Y>(a:T, b: Y) {
return `${a}${b}`
}
moreT<number, string>(1, '2')
// K extends keyof T,让泛型 K 继承 T 的 keyof 的属性
function getObjVal<T, K extends keyof T>(obj:T, key: K) {
return obj[key]
}
let o = {a: 1, b: 3, c: 4}
getObjVal(o, 'a')
getObjVal(o, 'd') // 'd' 不是 对象 o 下面的 keyof 属性。
上面泛型的使用规则不局限在类或函数内
十一.TS 配置项
tsconfig.json是 TS 编译成 JS 代码的辅助文件
- 调用 tsconfig.json 文件直接使用 tsc 命令即可,需要注意的是这样会编译所有 ts 文件
"compilerOptions"
是各编译的选项,可以添加额外的配置项比如 "include"指定要编译的 TS 文件, "exclude"指定不要编译的 TS 文件,示例"include": ["/demo.ts"]
。
常用的 compilerOptions 编译配置项
"removeComments": true,
编译过程中自动去掉注释"noImplicitAny": true,
要求编译的 TS 代码不能带有未指定的类型,即便是any 也需要显示的写明"strictNullChecks": true,
强制进行 null 校验。"rootDir" 和 "outDir",
rootDir 是指定要 build 打包的文件,而 outDir 是 build 后的生成文件,两者后面都是跟指定的目录文件。"allowJs": true,
是将 js 文件也编译一遍,编译成 es5 的文件"checkJs": true,
能直接像 TS 一样检测 JS 的语法错误,不需要在运行后才提示有错"noUnusedLocals",
"noUnusedParameters"用来检测定义了却没有被使用的变量或函数参数。
十二.TS 高级技巧(持续更新中)
很感谢你能看到这里,希望这份教程能对你有一点点帮助 😊,我是林一一,下次见。
博客地址 体验效果更好。**
源码地址 欢迎start (issue),以后会不断的更新内容**
参考:《Typescript官网》《Typescript实战技巧》《你不知道的Typescript高级技巧》《Typescript高级技巧》《你不知道的Typescript泛型》《Typescript深入浅出》。。。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。