Typescript笔记

typescript

typescript的一个特征就是静态类型检测。在JavaScript中,需要将代码运行到浏览器中才可以查看报错结果。
在这里插入图片描述
在typescript提供的编译环境中发现,在编写代码的阶段,就直接给出下划线错误提示信息。
Typescript笔记_第1张图片
typescript会对错误的拼写,函数未调用,逻辑错误等进行检测。

typescript基本认识

安装typescript:npm i typescript -g
创建一个1.ts的文件并添加代码。发现如果在编写代码环节就出现了某些错误,那么会直接给出错误的提示信息。
Typescript笔记_第2张图片
之后在控制台输入tsc ts文件,这个时候发现,该命令会生成一个同名的js文件,并对ts中的代码进行处理过渡到js中。但是这个时候会出去一个问题,如图,在ts文件中却出现了爆红提示信息。这是因为ts和js中变量名冲突引起的
Typescript笔记_第3张图片
解决ts和js变量名冲突问题,可以使用tsc --init生成一个配置文件解决。但是修改完毕后,又会出现一个新的提示报错信息,该信息暂时通过生成的初始化文件修改其中的 "strict": false,解决。
Typescript笔记_第4张图片
这个时候在原先输出结果是1,2的基础上,将1修改为‘你好’,再次运行js文件,但是依旧输出原先的内容,这是因为ts更新后的内容没有被编译,需要重新手动编译。如何自动检测到ts文件的修改重而自动运行tsc --watch。但是开启了自动更新至js文件的时候,如果ts代码检测出错误,依旧会同步到js文件中。
Typescript笔记_第5张图片

如果我们希望,在ts文件中,如果发出错误警告,就停止更新js文件,就需要添加命令:tsc --noEmitOnError --watch解决。通常该命令都是配合自动更新watch一起出现。单独出现无意义
Typescript笔记_第6张图片

显示类型

在上面的例子中,如果在严格模式下,没有给形参定义类型,那么就会报错。如果指定了其类型值,就叫做显示类型。typescript具有自动推断类型的功能,并不是所以的值都需要添加类型。
Typescript笔记_第7张图片
能够自动推断str的类型
Typescript笔记_第8张图片

降级编译

有些ES6的语法,在低版本的浏览器中无法被处理,但是在ts转换为js的时候又默认以es6 的语法作为基础不进行转换。这个时候就需要手动处理了。修改配置文件中的"target": "es5"
这个时候就会发现原先es6的字符串模板就会降级成es5的语法,两者之间相互兼容。
在这里插入图片描述

严格模式

当开启strict:true的时候,如果typescript无法推测出一个类型值的时候,需要手动赋类型值,否则就会提示any类型的错误,这样子和原生的JavaScript效果是一致的,any退化为普通的js,但是在typescript中这样子的代码无意义。

除了有strict属性还有noImplicitAnystrictNullChecks两个属性。strict属性包含了这两个属性的功能,
noImplicitAny:true的时候,当检查出any类型的时候,就会给出报错提示。
Typescript笔记_第9张图片

strictNullChecks:true的时候,undefined不能隐式推断出一个string类型的,所以给出报错提示。
在这里插入图片描述

常用类型

基元类型

像string,number,boolean这种就是基元类型。typescript支持在定义类型的同时赋值。
可以修改配置文件中的 "rootDir": "./src"修改入口文件,"outDir": "./dist"修改输出的文件位置

let num: number = 123
let str: string = 'hello'
let bol: boolean = true

数组

数组的写法有两种:type[]Array.其中type是需要声明的类型。

let arr1: number[] = [1, 2, 3] //声明一个数组,每个数组元素都是number型
// arr1 = ['a'] //企图将一个字符串赋值给number型

let arr2: Array<number> = [1, 2, 3] //通过泛型声明一个数组

any

当使用any定义类型的时候,就代表不希望某个特定值导致类型检测错误。但在开发中,这么做毫无意义,无法帮助我们避免错误发生。

let obj: any = {
  x: 1
}
obj.y = 100
obj.getX() //运行js文件会报错,无该函数

变量上的类型注释

就是指明了一个类型值后,不能将其他的类型值再赋值给该变量。

let str: string = 'hello'

函数

函数可以声明返回值类型,但是通常可以省略。如下代码中void和number就是可以省略的,效果不变。

function greet(name: string): void { //可以指定返回值类型
  // 函数的返回值类型通常typescript能够自动推断出不需要手动写
  console.log("hello " + name + " !")
}

greet('张三')
function getSum(): number {
  return 100 * 666
}

getSum()
let arr: number[] = [1, 2, 3]
//下面两个函数中item的类型是根据上下文自动推断出来的
// forEach本身就是遍历数组中的每一个元素,而元素又被限定为number型,所以item也会自动推断为number型
arr.forEach(function (item) {
  console.log(item)
})
arr.forEach(item => {
  console.log(item)
})

对象类型

这里的对象是作为函数参数的时候如何使用,需要对对象内部的值进行类型定义

function setXY(point: { x: number, y: number }) {
  console.log(`坐标x为:${point.x},坐标y为:${point.y}`)
}
setXY({ x: 1, y: 2 })

如果某些参数不想传入的时候,可以使用?号代替,?号代表改值为可选项,可能为undefined或者其他类型,在调用的该属性身上的一些特定方法的时候需要添加?.es6的语法去调用,如果为undefined就不再往后执行

function setName(name: { first: string, last?: string }) {
  console.log(name.last?.toUpperCase())
}
setName({ first: "张三", last: "xx" })
setName({ first: "张三" })

联合类型

给一个变量赋予多个类型选择,多个类型之间使用|分隔。
如下代码中,id可以为数值型或字符串型,但是如果想调用字符串类型独有的方法的时候就会报错。该如何解决。

function getId(id: number | string) {
  //类型“string | number”上不存在属性“toUpperCase”。\n  类型“number”上不存在属性“toUpperCase”
  id.toUpperCase()
}
getId(1)
getId("123456")

修改代码如下

  if (typeof id === 'string') {
    id.toUpperCase()
  }
function getStr(str: string[] | string): void {
  if (Array.isArray(str)) {
    console.log(str.join(' - '))
  }
}

getStr('abcd')
getStr(["a", "b", "c", "d"])

上面代码都是某些类型独有的情况,下面代码是共有方法的情况使用,slice方法既存在字符串身上也存在数组身上

function getSlice(str: string | number[]): string | number[] {
  return str.slice(0, 2)
}
console.log(getSlice("abcdefg"))
console.log(getSlice([1, 2, 3, 4, 5, 6]))

类型别名

如果一个变量存在多个类型,且多个变量想复用这些类型,如果在每个变量后面都写上这些类型名是错误的,应该使用一种复用的思想,减少重复的代码。
使用type 类型名=类型声明一个需要被复用的类型名,其中type是关键字。

type XY = { //XY为类型变量名
  x: number,
  y: number
}

function getXY(xy: XY) {
  console.log(xy.x, xy.y)
}
getXY({ x: 1, y: 2 })

type ID = number | string
function getID(id: ID) {
  console.log(id);
}
getID(1)
getID("1")

同时也可以单独指定函数返回值的类型

type returnResultType = string
function getName(s: string): returnResultType {
  return '张三' + s
}
let res = getName("123")
// res = 123 //错误,res已被自动推断出类型
res = '123'

接口

定义接口的关键字interface

interface Per {
  name: string
  age: number
}

function getInfo(msg: Per) {

}
getInfo({ name: 'zz', age: 21 })

那么使用typeinterface定义的类型有什么区别?

  1. 扩展不同,在interface中使用extends关键字扩展类型,在type中使用&符扩展
interface Animal {
  name: string
}
interface Dog extends Animal {
  age: number
}

let dog: Dog = {
  //声明类型的时候,缺一不可
  name: '狗',
  age: 21
}
type Animal = {
  name: string
}
type Dog = Animal & {
  age: number
}
let dog: Dog = {
  name: '狗',
  age: 21
}
  1. 如何向现有类型添加新字段
    *:interface会将两个同名的接口进行融合
interface Animal {
  name: string
}
interface Dog extends Animal {
  age: number
}
// 现有基础上添加一个类型
interface Dog {
  more: string
}
let dog: Dog = {
  name: '狗',
  age: 21,
  more: '俺是狗'
}

而在type中不允许重复定义
Typescript笔记_第10张图片

类型断言

当typescript在编译的时候遇见一个不确定的类型值的时候,我们可以该值进行断言,保证该值为断言后面的类型。断言通常使用as关键字和<>两种写法。
类型断言和类型注释一样,由编译器删除,不会影响代码的运行时行为,所以没有运行时检查。

const r1 = document.querySelector("id") as HTMLCanvasElement
const r2 = <HTMLCanvasElement>document.querySelector("id") 

文字类型

文字类型是一种特殊的类型,它表示一个具体的字符串值,而不是字符串类型的所有可能值。例如,"hello"是一个文字类型,它只能表示"hello"这个字符串,而不是其他的字符串。文字类型可以用来定义一些有限的、确定的、常量的值,比如颜色、方向、事件名等。
Typescript笔记_第11张图片
在这里插入图片描述
Typescript笔记_第12张图片

数字类型

作用和文字类型一致

function getId(a: number, b: number): 0 | 1 | -1 {
  // 返回的值只能为1,-1,0
  return a > b ? 1 : a === b ? 0 : -1
}

布尔类型

作用和之前一致

let bo1: true = true
let bo2: false = false

常见类型与数字类型和文字类型组合

interface Width {
  width: number
}

function setWidth(w: Width | "auto") {
}

setWidth({ width: 100 })
setWidth("auto")

文字推断

当由typescript进行文字推断的时候,可能会引发错误。如下代码中,形参method是一个文字类型,明确其值必须是’GET’ | ‘POST’ | ‘PUT’,而req.method中定义的GET是一个字符串,字符串所表示的范围太广,无法精确标签为一个文字类型,所以添加文字推断。

function handleRequest(url: string, method: 'GET' | 'POST' | 'PUT') {}
const req = {
  url: 'http://www.baidu.com',
  method: 'GET'
}
handleRequest(req.url, req.method)

修改代码如下,三选一

  method: 'GET' as 'GET' | 'POST' | 'PUT'
  
  handleRequest(req.url, req.method as  'GET' | 'POST' | 'PUT')
  
  const req = {
  url: 'http://www.baidu.com',
  method: 'GET'
  } as const 
//as const是一种特殊的类型断言,它叫做常量断言(const assertion)。
//它的作用是让编译器把一个对象或数组的类型推断为最具体的类型,而不是最宽泛的类型。
//当设置as const的时候,该值就为只读的,不能重新修改了

null和undefined类型

在这里插入图片描述

let res1: undefined = undefined
let res2: null = null

function test(str: string | null) {
  if (str === null) {

  } else {
    str.toUpperCase()
  }
}

在下面的代码中,如果确定str的值一定是number值不会为其他类型了,可以使用!符号断言,告知typescript该值一定不是其他类型了。但是一般情况下不建议使用,会出现一些意想不到的错误。

function test1(str?: number | null) {
  console.log(str!.toFixed())
}

枚举

枚举是 TypeScript 为数不多的功能之一,它不是 JavaScript 的类型级扩展。

枚举允许开发人员定义一组命名常量。 使用枚举可以更轻松地记录意图或创建一组不同的案例。 TypeScript 提供基于数字和字符串的枚举。如果没有指定初始值,默认从0开始

enum Direction {
  Up = 1,
  Down,
  Left,
  Right,
}

console.log(Direction.Up) //1
console.log(Direction.Down) //2

由于JavaScript中没有枚举,所以ts代码会转换为如下结果
Typescript笔记_第13张图片

BigInt和Symbol

const bigint1: bigint = BigInt(1)
const bigint2: bigint = 1n

const name1 = Symbol("张三")
const name2 = Symbol("张三")

类型缩小

原本first的类型是联合类型number和string,但是在经过一次if判断后,first的类型在分支中被细化了。可以简单的理解为类型缩小。

function test(first: number | string, second: string): string {
  if (typeof first === 'number') {
    return new Array(first + 1).join(' ') + second
  } else {
    return first + second
  }
}

typeof类型守卫

使用typeof关键字能够返回的结果如下
Typescript笔记_第14张图片

    let arr = ["123"]
    function fun() {
    }
    let s1 = Symbol("name")
    console.log(typeof arr) //object
    console.log(typeof null)//object
    console.log(typeof undefined)//undefined
    console.log(typeof "!23")//string
    console.log(typeof 123)//number
    console.log(typeof true)//boolean
    console.log(typeof fun) //function
    console.log(typeof s1) //symbol

使用typeof进行类型守卫,缩小类型范围。但是如下代码会报一个错误,就是str可能会为空,这是因为null也属于object,所以在第一个if判断的时候就进入了不再往后执行

function test(str: string | string[] | null) {
  if (typeof str === 'object') {
    for (const item of str) { }
  } else if (typeof str === 'string') {
    console.log(str);
  } else {
  }
}

真值缩小

当在if条件中遇见如:0,NaN,“”,0n,null,undefined的时候,会自动转换为false。减小了范围。
将上面的代码使用真值缩小解决问题,在第一个判断前添加代码如下,代表如果为null,第一个判断条件就不会成功进入

function getAll(str: string | string[] | null) {
  if (str && typeof str === 'object') {
    for (const i of str) {
      console.log(i);
    }
  } else if (typeof str === 'string') {
    console.log(str)
  } else {
  }
}

下面的代码中使用!符号,将false转换为true进入判断。减小了类型的范围

function multiplyAll(item: number[] | undefined, price: number) {
  if (!item) {
    return item
  }
  return item.map((each) => {
    return each * price
  })
}
console.log(multiplyAll([1, 2, 3], 5))
console.log(multiplyAll(undefined, 10))

等值缩小

如下这段代码,想在x和y身上调用字符串的方法。但是会出现一个问题,即每一个变量都存在两个类型,如何缩小类型范围。

function example(x: string | number, y: string | boolean) {
//类型“string | number”上不存在属性“toUpperCase”。类型“number”上不存在属性“toUpperCase”。
  x.toUpperCase()
  y.toUpperCase()
}

修改为如下代码,当x和y全等的时候,就代表x和y都为string类型了,缩小了类型范围。

  if (x === y) {
    x.toUpperCase()
    y.toUpperCase()
  }

在下面的代码中,在if条件中进行了一次不等于判断过滤

interface Container {
  value: number | null | undefined
}

function multiply(container: Container, params: number) {
  if (container.value != null) {
    console.log(container.value)
  }
}
multiply({ value: 5 }, 10) //5
multiply({ value: undefined }, 10) //undefined和null均未执行输出
multiply({ value: null }, 10)

这里需要注意:!=的时候,只会比较值,不比较类型

console.log(5 == '5')//true
console.log(undefined != null);//false
console.log(null != null);//false

in操作符缩小


如果指定的属性在指定的对象或其原型链中,则 in 运算符返回 true。,只会判断身上是否有该属性

const car = { make: 'Honda', model: 'Accord', year: 1998 };

console.log('make' in car);
// Expected output: true

如下代码中,使用in操作符判断,这个时候由于typescript可以推断出具体的类型值,所以未出错

type Fish = {
  swim: () => void
}
type Bird = {
  fly: () => void
}

function ex2(animal: Fish | Bird) {
  if ("swim" in animal) {
    console.log("swim" in animal)
    return animal.swim()
  }
  return animal.fly()
}

添加一个人的类型定义,并添加到函数的类型中,发现会报错。原因是swim的时候,animal的类型可能是Fish后者People的。但是People中的swim类型又可能为空,所以报错

type People = {
  // 可选项
  swim?: () => void,
  fly?: () => void
}

Typescript笔记_第15张图片
修改代码如下,解决了错误问题。使用(animal as Fish).swim()断言为Fish,缩小了类型范围

function ex2(animal: Fish | Bird | People) {
  if ("swim" in animal) {
    // animal: Fish | People
    return (animal as Fish).swim()
  }
  // animal: Bird | People
  return (animal as Bird).fly()
}

instanceof操作符缩小

使用instanceof关键字判断一个属性是不是存在于某一个原型链上。可以缩小类型范围

function instanceTest(time: Date | string) {
  if (time instanceof Date) {
    console.log(time.toUTCString())
  } else {
    console.log(time)
  }
}

instanceTest(new Date())
instanceTest("hello")

分配缩小

分配缩小是指 TypeScript 根据赋值的值来缩小变量的类型的过程。

//let res1 : number | string 
let res1 = Math.random() < 5 ? 10 : "你好"
// let res1 : number
res1 = 1
// let res1 : string
res1 = "hello"

// let res1 : boolean 错误res1的类型已经缩小确定了
// res1 = true

控制流分析

控制流分析是指 TypeScript 根据代码的逻辑结构和执行路径来推断变量的类型的过程。
在下面代码中,x 的类型是 string | number | boolean,当你给它赋值为 Math.random() < 0.5 时,它就被缩小为 boolean 类型。在 if…else 语句中,每次判断 时,它就会被缩小为对应的类型分支,比如 string 或 number在最后返回 x 时,它的类型是 string | number,因为 boolean 类型已经被排除了。这样,TypeScript 就可以根据控制流分析来检查变量的类型是否正确。所以在程序的最后将布尔值赋值给变量的时候就会报错。

function example() {
  // 初始定义x的类型范围
  let x: string | number | boolean
  // x : Boolean
  x = Math.random() < 0.5

  if (Math.random() < 0.5) {
    // x: number
    x = 100
  } else {
    // x: string
    x = 'hello'
  }
  return x
}

let x = example()
x = 100
x = '1'
// x = true 错误 x的类型已经缩小为string和number了

使用类型谓词

如下代码中 pet is Fish就是类型谓词,其中pet参数必须存在于函数中,根据返回值决定类型。若返回true,则pet为Fish,若为false,则pet为Bird。

type Fish = {
  name: string,
  swim: () => void
}

type Bird = {
  name: string,
  fly: () => void
}

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim != undefined
}

继续添加如下代码

function getSmallPet(): Fish | Bird {
  let fish: Fish = {
    name: '鱼',
    swim: () => { }
  }
  let bird: Bird = {
    name: '鸟',
    fly: () => { }
  }
  return true ? bird : fish
}

let pet = getSmallPet()

if (isFish(pet)) {
  pet.swim()
} else {
  pet.fly()
}

下面是类型谓语的使用

const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()]

// 两者过滤写法,自动调用函数,将zoo的每一个参数作为实参传递
const underWater: Fish[] = zoo.filter(isFish)
const underWater2: Fish[] = zoo.filter(isFish) as Fish[]
// 等价于下面这种写法,下面这种是类型谓语的嵌套
const underWater3: Fish[] = zoo.filter((pet): pet is Fish => {
  if (pet.name === 'frog') {
    return false
  }
  return isFish(pet)
})

受歧视的union

如下Shape类型的定义,本意是针对不同的形状,可以拥有不同的属性,但这么写在一个type中就会存在一个问题,即。半径和边长都边长了可选项

type Shape = {
  kind: "circle" | "square", //定义文字类型
  radius?: number,
  sideLength?: number
}

解决下面这段代码提示可能没有该属性,可以添加一个!符号解决。
Typescript笔记_第16张图片
可以将上面的类型定义分离,设计为不存在空属性即可,但是最终还是会存在无该属性的问题,所以可以使用类型缩小的方式解决

interface Circle {
  kind: 'circle',
  radius: number
}
interface Square {
  kind: 'square',
  sideLength: number
}

type Shape = Circle | Square

function getArea(shape: Shape) {
  if (shape.kind === 'circle') {
    return Math.PI * shape.radius ** 2
  }
}

never类型与穷尽性检测

never类型是一种特殊的类型,它表示一个永远不会发生的值,或者一个永远不会返回的函数,一个不存在的状态。它是所有类型的子类型,但是没有任何类型是它的子类型(除了never本身)这意味着你可以把never类型赋值给任何类型,但是不能把任何类型赋值给never类型。

穷尽性检测是一种利用never类型来确保我们处理了所有可能的情况的技巧。
注意下面这段代码,这种情况下不会报错,因为shape不是Circle或Square,那么到default的时候,就是一个永远不会发生的值了。一个永远不会发生的值可以赋值给never类型的变量。但是如果shape确实是Circle或Square之外的其他类型,那么TypeScript就会报错,因为那个类型不能赋值给never类型。这样就可以确保我们处理了所有可能的Shape类型,没有遗漏任何一种情况。

interface Circle {
  kind: 'circle',
  radius: number
}
interface Square {
  kind: 'square',
  sideLength: number
}

type Shape = Circle | Square

function getArea(shape: Shape) {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2
    case 'square':
      return shape.sideLength ** 2
    default:
      const params: never = shape
      return params
  }
}

如下图,就是将一个非never的类型值赋值给了never,导致了报错。default执行到的时候,shape可能为triangle。
Typescript笔记_第17张图片

函数

函数类型表达式

function print(str: string) {
  console.log("打印:" + str)
}

// 将函数作为参数
function operator(fn: (str: string) => void) {
  fn("你好")
}

operator(print)

也可以将参数的类型定义提取出来定义

type Fn = (str: string) => void
// 将函数作为参数
function operator(fn: Fn) {
  fn("你好")
}

调用签名

如下定义了一个函数类型的变量名,并且添加一个签名属性description。需要注意的是:函数类型声明的时候使用:隔开
Typescript笔记_第18张图片

type Description = {
  description: string,
  (num: number): number //定义一个函数类型,参数类型和返回值类型
}

function doSome(fn: Description) {
  console.log("打印:" + fn.description + fn(66))
}

function fn(num: number): number {
  return num
}
// 设置函数的签名
fn.description = '你好'

doSome(fn)

构造签名

构造签名是一种用来表示构造函数类型的语法,它用new关键字来表示,后面跟着参数类型和返回值类型
Typescript笔记_第19张图片

class Ctor {
  str: string
  constructor(s: string) {
    this.str = s
  }
}

type SomeConstructor = {
  // 定义一个构造签名
  new(s: string): Ctor
}

function fn(ctor: SomeConstructor) {
  return new ctor("你好")
}
const f = fn(Ctor)

console.log(f.str)

泛型函数

在如下的代码中,本质希望res的类型是由调用函数的返回值决定(传入字符串类型返回字符串类型)。但是接收的形参数组arr类型为any类型,所以想返回第一个数组元素并非字符串类型而是any类型,导致了res为any类型。
Typescript笔记_第20张图片
下面这段代码定义了一个泛型函数,<泛型名>随意取

function FirstElement<Type>(arr: Type[]): Type | undefined {
  return arr[0]
}
/* 这段代码不能直接返回一个数字类型的100,而是返回数组元素,是因为函数的返回类型和参数类型是一致的 */
// 调用函数的时候隐式推断类型为number给Type
let res = FirstElement([1, 2, 3]) //res:number
// 调用的时候显示类型
let res1 = FirstElement<string>(['1', '2']) //res1:string
let res2 = FirstElement([]) //res2:undefined

也可以定义多个泛型。其中函数调用的时候传递一个字符串数组和一个转换整型的函数。(n) => parseInt(n)每次被遍历的时候将["1", "2"]的每个元素传入,所以该函数的形参arg类型为Input。所以最后res是一个类型为number的数组

function map<Input, Output>(arr: Input[], fun: (arg: Input) => Output): Output[] {
  return arr.map(fun)
}

const res = map(["1", "2"], (n) => parseInt(n))

泛型函数-限制条件

通常在定义泛型的时候定义约束条件:。限制条件是必须满足,代码中,数组和字符串都具有length属性不会报错,但是数值不具备条件,所以报错。

function fun<Type extends { length: number }>(a: Type, b: Type) {
  // 泛型本身可能不具备length属性
  if (a.length > b.length) {
    return a
  } else {
    return b
  }
}

let res3 = fun([1, 2, 3], [4, 5, 6])
let res4 = fun("feiyu", "niuhu")
// let res5 = fun(1, 2) 错误,number身上没有length属性

泛型函数-使用受限制

如下代码中,存在一个问题,如果返回的是arr的时候就可以去调用length属性,并且返回的是泛型类型。但是下面代码中返回的是一个对象,该对象不属于定义泛型的范围内(number数组),所以会报错
Typescript笔记_第21张图片

泛型函数-指定类型参数

这里泛型type的类型可能为string或number,是一种联合关系。如果不显示的指定combine()类型,则type泛型默认接收一个类型,当接收number型数组的时候,又再次接收字符串数组,就会报错。

function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
  return arr1.concat(arr2)
}

//res6 : number | string
let res6 = combine<number | string>([1, 2, 3], ["string"])

console.log(res6)

函数-参数默认值

如下代码中,给一个形参赋值默认值,当指定一个参数默认值的时候不应该再次有:?

function test(n: number = 100) {
  console.log(n.toFixed())
}

test(18.18)
test()

函数-回调中的可选参数

如果为回调函数写函数类型的时候,尽量不要写一个可选参数。会发送不可预估的问题。
下面这段代码中,回调函数类型定义的时候,将index作为可选项配置。如下第一段代码没有问题

function test9(arr: any[], callback: (item: any, index?: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i)
  }
}
test9([1, 2, 3], (n) => console.log(n))
test9([1, 2, 3], (n, i) => console.log(n, i))

当代码修改为如下的时候可能会出错。i被允许为可选项,但是在没有被传递数据的情况下却想使用,所以就会报错。

function test9(arr: any[], callback: (item: any, index?: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i])
  }
}
test9([1, 2, 3], (n, i) => {
  i.toFixed()
})

函数重载

实现不同参数输入并且对应不同参数的输出函数。定义多个重载签名,一个实现签名。重载签名主要是精确显示函数的输入输出,实现签名主要是将所有的输入输出类型做一个全量定义。
重载签名是指函数重载中的每一个函数类型定义,它们只包含函数名称、参数列表和返回值类型,不包含函数体。
实现签名是指函数重载中的最后一个函数类型定义,它包含函数名称、参数列表、返回值类型和函数体。它的参数类型和返回值类型必须与重载签名兼容,否则会报错

function test10(timestamp: number): Date
function test10(y: number, m: number, d: number): Date
// 该实现签名接收1个或三个参数
function test10(yortimestamp: number, m?: number, d?: number): Date {
  if (d !== undefined && m !== undefined) {
    return new Date(yortimestamp, m, d)
  } else {
    return new Date(yortimestamp)
  }
}

test10(132456)
test10(5, 6, 7)
// test10(1, 2) 错误,无法接收2个参数

重载签名和实现签名注意点

如下代码中,定义了一个重载签名,他需要一个字符串类型作为参数,在实现签名中并没有创建一个参数。调用的时候企图不传递参数的情况下直接使用发现会报错,这是因为在调用的时候,看不见实现函数中签名的约束,只能看见重载函数中签名的约束条件,所以必须传入一个参数
Typescript笔记_第22张图片
需要让实现签名的函数类型完全兼容重载签名的函数

function fn(x: string): void
function fn(x: boolean): void
function fn(x: string | boolean) {
}

修改一下上面的代码,让他们具有不同的返回值类型,会发现代码提示错误,是因为实现签名的返回值为void,无法兼容两个重载签名。
Typescript笔记_第23张图片
如果手动返回类型的话,会推断类型值只有一种字符串类型,因此又不兼容第二种情况,所以需要添加函数的返回值类型
Typescript笔记_第24张图片
这样子,实现签名才兼容重载签名

function fn(x: string): string
function fn(x: boolean): boolean
function fn(x: string | boolean): string | boolean {
  return 'true'
}

编写好的重载

如下这段代码,目前函数调用传入的类型符合重载签名的要求。

function test12(s: string): number
function test12(arr: any[]): number
function test12(arg: any) {
  return XMLDocument.length
}

test12("hello")
test12([1, 2, 3])

继续添加第三段函数调用语句。当添加了如下这段代码后就会报错,是什么原因呢?这是因为在这里调用的时候推断出了参数的类型为联合类型string | number[]。无法满足重载签名的约束,所以报错。

test12(true ? "hello" : [1, 2, 3])

Typescript笔记_第25张图片
代码修改如下,就不会报错了,且代码量也会减少

function test12(args: string | any[]): number {
  return args.length
}
test12("hello")
test12([1, 2, 3])
test12(true ? "hello" : [1, 2, 3])

函数内this的声明

interface User {
  admin: boolean
}
// 定义一个接口,其中this的类型是User
interface DB {
  filterUsers(filter: (this: User) => boolean): User[]
}

const db: DB = {
  filterUsers: (filter: (this: User) => boolean) => {
    let u1: User = {
      admin: true
    }
    let u2: User = {
      admin: false
    }
    return [u1, u2]
  }
}

const res13 = db.filterUsers(function (this: User) { //不能使用箭头函数
  return this.admin
})
console.log(res13) //[ { admin: true }, { admin: false } ]

了解其他类型

  • void

如下代码,返回一个undefined,但实际该函数推断出来void。

function test14() {
  return;
}
  • object
    Typescript笔记_第26张图片
  • unknow
    unknown类型代表任何值。与any类似,但比any安全,因为对unknown值做任何操作是不合法的。
    Typescript笔记_第27张图片
  • never
    never表示永远不会被观察到的值
  • Function
    全局性的Function类型描述了诸如bind,call,apply和其他存在于JavaScript中所有函数值的属性。还有一个特殊的属性,即Function类型的值总是被调用的,这些调用返回any。

参数展开运算符

当不确定需要准备多少个形参接收的时候,可以使用剩余参数接收,使用剩余参数接收的实参会被组成一个数组,所以类型定义一定是数组

function t15(n: number, ...args: number[]) {
  return args.map(item => item * n)
}

console.log(t15(10, 1, 2, 3, 4))

下面这段代码将两个数组合并,目前没有问题,将一个number类型的值插入到number数组中

const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
arr1.push(...arr2)
console.log(arr1)

但是下面的这段代码就会出现问题了,在Math.atan2()中需要两个参数,而展开运算符的时候并不知道会展开多少个参数,所以才报错

const a1 = [1, 2]
console.log(Math.atan2(...a1))

修改如下,错误消除,断言为两个常量

const a1 = [1, 2] as const
console.log(Math.atan2(...a1))

参数解构

function t16({ a, b, c }: { a: number, b: number, c: number }) {
  console.log(a + b + c)
}

t16({a: 1,b: 2,c: 3})

返回void类型

一个具有void类型的上下文函数类型,在实现的时候,可以返回任何值,但都会被忽略。如下代码中,最终变量的类型均为void,而非boolean

type fn = () => void

const f1: fn = () => {
  return true
}
const f2: fn = () => true
const f3: fn = function () { return true }
const rf1: void = f1()
const rf2 = f2()
const rf3 = f3()

但是一个函数定义了一个具有viod返回类型的时候,该函数必须不返回任何东西。
在这里插入图片描述

对象

// 对象类型字面量/匿名
function f1(person: { name: string, age: number }): string {
  return `hello,${person.name},你今年${person.age}岁了。`
}

//通过接口方式
interface Person {
  name: string,
  age: number
}

function f2(person: Person) {
  return `hello,${person.name},你今年${person.age}岁了。`
}

// 通过类型别名方式
type Person2 = {
  name: string,
  age: number
}

function f3(person: Person2) {
  return `hello,${person.name},你今年${person.age}岁了。`
}

可选参数

下面这段代码,虽然在编译的时候并没有报错,但是我们知道,何时xPos和yPos的值为undefined。那么我们该如何避免这种情况发生。即当遇见这种情况的时候就直接使用默认值0.

type Shape = {}

interface PaintOptions {
  shape: Shape,
  xPos?: number,
  yPos?: number
}

function paintShape(opts: PaintOptions) {
  let xp = opts.xPos
  let yp = opts.yPos
  console.log(xp, yp)
}

const shape: Shape = {}
paintShape({ shape })
paintShape({ shape, xPos: 100 })
paintShape({ shape, yPos: 100 })
paintShape({ shape, xPos: 100, yPos: 100 })

解决1方法如下

function paintShape(opts: PaintOptions) {
  let xp = opts.xPos ? opts.xPos : 0
  let yp = opts.yPos ? opts.yPos : 0
  console.log(xp, yp)
}

解决2方法如下。如下方法中采用对象解构的同时并赋值,但是需要注意的是如下number并非类型名,而是解构后重新赋予的别名。所以在对象解构的时候,不能去进行类型注释。

function paintShape({ shape, xPos: number = 0, yPos = 0 }: PaintOptions) {
  console.log(shape, number, yPos)
}

只读属性

使用readonly关键字修饰的属性会变为只读的。不能重新赋值。
Typescript笔记_第28张图片
Typescript笔记_第29张图片
需要注意:将p1的值赋值给了p2,最终结果输出21,22。会发现虽然不能直接修改只读的属性,但是可以修改原p1的值,会发现p2也会被修改。readonly修饰符只是用于编译时检查属性的赋值,它不会影响JavaScript运行时的行为。也就是说,readonly只是告诉TypeScript编译器,这个属性不能被重新赋值,但是在JavaScript中,它仍然可以被修改
Typescript笔记_第30张图片

索引签名

如下代码定义了一个具有索引的签名,主要对数组进行约束。其中string定义约束的是数组的每一个元素。number约束的是数组的索引使用,如a1[0]

interface StringArray {
  [index: number]: string
}

let a1: StringArray = ["a", "b"]
let a11: StringArray = {
  1: "a",
  2: "b",
  // 3:3 错误
}
a1[0] = 'c'

如下这段代码,主要对对象进行约束。其中props约束的对象中的键名类型,对象中键名的全写形式为’a’:1的格式。number约束值的类型。

interface TestString {
  [props: string]: number
}

let a2: TestString = {
  a: 1,
  b: 2,
  // c:'a' 错误
}

如下代码中,如果添加一个属性 name: string,并指定类型为string的时候就会报错,因为不符合索引定义的要求

interface NotOK {
  [index: string]: number,
  length: number, //符合上面的键值对要求
}

解决方案

interface NotOK {
  [index: string]: number | string,
  length: number, //符合上面的键值对要求
  name: string
}

定义使用如下,其中length和name为必填属性

let notOk: NotOK = {
  length: 6,
  name: 'string',
  a: 1,
  b: "1"
}

使用索引签名可以更好的对对象进行约束使用。索引签名表示这个接口的对象可以有任意数量的额外属性,只要它们的属性名,属性值符合约束即可。

扩展类型

当有一个现有类型的时候,需要一个新的类型接口去继承扩展它,就可以使用extends关键字

interface Base {
  name?: string,
  city: string
}

interface Person extends Base {
  age: number,
  sex: string
}

let p1: Person = {
  name: 'zq',
  city: '上海',
  age: 21,
  sex: "男"
}

可以使用extends关键字继承多个接口类型使用

interface Color {
  color: string
}
interface Shape {
  shape: string
}

interface Circle extends Color, Shape {
  size: number
}

let c1: Circle = {
  size: 33,
  color: 'red',
  shape: 'circle'
}

交叉类型

如下代码中,type ColorCircle = Color & Circle就是一个交叉类型定义的例子。

interface Color {
  color: string
}

interface Circle {
  shape: string
}

type ColorCircle = Color & Circle

let c1: ColorCircle = {
  color: 'red',
  shape: 'circle'
}

在定义函数参数的时候,也可以使用交叉类型的方式定义

function c12(c1: Color & Circle) {
  console.log(c1.color, c1.shape)
}

c12({
  color: "red",
  shape: "circle"
})
c12({
  color: "red",
  shapee: "circle" 报错,不存在shapee
})

泛型对象

如果定义多个对象,这些对象的格式基本相同,是否可以采用如泛型的概念优化代码。如下代码中,定义了多个接口对象,每一个接口的变量类型不同但属性名相同。

interface StringX {
  x: string
}
interface NumberX {
  x: number
}
interface BooleanX {
  x: boolean
}

let pos1: StringX = {
  x: "1"
}
let pos2: NumberX = {
  x: 1
}
let pos3: BooleanX = {
  x: true
}

如下代码中,定义了一个泛型接口实现泛型对象,定义了一个模板,就可以实现不同类型的变量定义

interface PublicX<Type> {
  x: Type
}
let pos1: PublicX<string> = {
  x: "1"
}
let pos2: PublicX<number> = {
  x: 1
}

一个接口类型作为泛型存在

// 定义泛型对象,类型是一个接口类型
interface Box<Type> {
  ctx: Type
}

interface Apple { }
let a: Apple = {}
type AppleBox = Box<Apple>

let apple: AppleBox = {
  ctx: a
}

也可也使用类型别名定义泛型对象

type Box<Type> = {
  ctx: Type
}
type orNull<Type> = Type | null //空或其他
type oneOrMany<Type> = Type | Type[] //单个或数组
type oneOrManyOrNull<Type> = orNull<oneOrMany<Type>>
type oneOrManyOrNullOrString = oneOrManyOrNull<String>

类型操作

从类型中创建类型分以下七种:泛型类型,keyof类型操作符,typeof类型操作符,索引访问类型,条件类型,映射类型,模板字面量类型。

使用通用类型变量

在TypeScript中,使用通用类型变量是一种创建泛型的方法。泛型是一种让一个组件可以适用于多种类型的工具,而不是只能适用于一个类型。 通用类型变量使用来表示,它可以记住用户提供的类型,并且只和那个类型一起工作。
如下这段代码,在js中可以运行,但是在ts中会在编译运行的时候报错。因为并不是所有传入的值都具有length属性。

function fn1<Type>(params: Type): Type {
  console.log(params.length) //会报错
  return params
}

fn1('hello')

修改如下就不会出现报错提示了

function fn1<Type>(params: Array<Type>): Type[] {
  console.log(params.length) //可以推断出数组一定具有长度属性
  return params
}

fn1([1, 2, 3])

泛型类型

给定一个函数的基本形式并将函数赋值给一个变量,需要写出对应的类型定义形式

function test2<Type>(args: Type): Type {
  return args
}

方法一

let x1: <Type>(args: Type) => Type = test2

方法二,当使用如下写法的时候,发现可以将类型定义单独定义一个接口使用

//对象字面量的形式
let x2: { <Type>(args: Type): Type } = test2
interface Pub {
  <Type>(args: Type): Type
}

let x3: Pub = test2

方法三:使用下面这种定义形式,约束能力比上面的强,会强制用户变量赋值的时候就指定类型,而不是由typescript自行推断。

interface Pub<Type> {
  (args: Type): Type
}

let x4: Pub<string> = test2

泛型类

泛型类是一种使用类型变量来表示类中某些属性或方法的类型的类。它们可以让你定义一个类,而不是指定具体的数据类型,而是使用一个类型变量来代表任意类型。这样,你就可以在不同的类型之间保持一致性和灵活性。
定义如下的泛型类,会发现报错提示,该提示可以通过配置文件暂时关闭
在这里插入图片描述

class Generic<Type> {
  num: Type
  getSum: (x: Type) => Type
}

let myGenericNumber = new Generic<number>()
myGenericNumber.num = 0
myGenericNumber.getSum = function (x) {
  return x
}
//可以快速适用于多个类型
let myGenericNumber = new Generic<string>()
myGenericNumber.num = '0'
myGenericNumber.getSum = function (x) {
  return x
}

泛型约束

定义一个如下函数,并传入一个参数,这个时候我们尝试调用该参数身上的length属性会给出如下提示信息。typescript不确定用户会传入什么类型,是否具有length属性,所以这里我们需要添加泛型约束。

function GenericFun<Type>(args: Type): Type {
  args.length //类型“Type”上不存在属性“length”。
  return args
}

如下代码中,编译器会检查传入的 Type 类型是否有 length 属性。使用了泛型约束 Type extends lengthSize ,它要求 Type 必须继承自 lengthSize 接口,也就是说 Type 必须具有 lengthSize 接口定义的所有属性和方法

interface lengthSize {
  length: number
}

function GenericFun<Type extends lengthSize>(args: Type): Type {
  args.length
  return args
}

GenericFun('hello') //string
GenericFun([1, 2, 3]) //number[]
// GenericFun(1) //报错 number

在泛型约束中使用类型参数

如下代码中,在定义泛型的时候了extends keyof关键字定义,代表Key泛型属于Type泛型。

function fun5<Type, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key]
}
let obj = {
  a: 1,
  b: 2,
  c: 3
}
fun5(obj, 'a')
fun5(obj, 'b')
// fun5(obj, 'm') 类型“"m"”的参数不能赋给类型“"a" | "b" | "c"”的参数。

在泛型中使用类类型

class BeeKeeper {
  hasMask: boolean = true
}

class ZooKeeper {
  tagName: string = 'hello'
}

class Animal {
  legs: number = 4
}

class Bee extends Animal {
  keeper: BeeKeeper = new BeeKeeper()
}
class Lion extends Animal {
  keeper: ZooKeeper = new ZooKeeper()
}

function fun6<A extends Animal>(c: new () => A): A {
  return new c()
}

fun6(Bee).keeper
/* 类型“typeof ZooKeeper”的参数不能赋给类型“new () => Animal”的参数。
  类型 "ZooKeeper" 中缺少属性 "legs",但类型 "Animal" 中需要该属性。 */
// fun6(ZooKeeper)

使用keyof类型操作符

keyof 是 TypeScript 中的一个关键字,它用于从一个对象类型中提取出它的键的类型。当用于一个有明确键的对象类型时,keyof 会创建一个由这些键组成的联合类型。
Typescript笔记_第31张图片

type Point = {
  x: number,
  y: number
}

type A = keyof Point //"x" | "y"
const Ax: A = 'x'
const Ay: A = 'y'
// const Az: A = 'z' 不能将类型“"z"”分配给类型“keyof Point”。

使用索引签名的时候,keyof关键字将键取出组成类型,而此时键的类型为number。

type ArrayNum = {
  [index: number]: number
}
type A = keyof ArrayNum

const a: A = 0
// const b: A = '0'不能将类型“string”分配给类型“number”。

当索引签名中索引为string类型的时候,使用keyof关键字取出键并组成新的类型,但是这个时候新类型是一个联合类型:string | number。区分上一个代码

type ArrayStr = {
  [index: string]: string
}
type B = keyof ArrayStr

const b1: B = 'a'
const b2: B = 1
// const b3: B = true 不能将类型“boolean”分配给类型“string | number”。

typeof类型操作符

let s = 'hello'
let news: typeof s //string
news = 'world'
// news = 100 不能将类型“number”分配给类型“string”。

ReturnType 是 TypeScript 中的一个内置的工具类型,它用于提取一个函数类型的返回值类型。ReturnType 接受一个类型参数 T ,它必须是一个函数类型

type Predicate = (x: unknown) => boolean
type K = ReturnType<Predicate>

下面这段代码是使用ReturnType的一个例子,fn8不是一个函数类型,而是一个函数名,直接给ReturnType会报错。
Typescript笔记_第32张图片
使用typeof操作符获取fn8函数的类型即可解决问题

function fn8() {
  return {
    x: 1,
    y: 2
  }
}

type P = ReturnType<typeof fn8>

注意点:在typescript中,typeof它只在标识符(即变量名称)或其属性上使用是合法的
如下代码中,因为 typeof msg() 不是一个类型,而是一个值

function msg() { }
let res: typeof msg()

修改如下

function msg() { }
let res: typeof msg

索引访问类型

使用索引访问类型在另一种类型上查找特定属性
Typescript笔记_第33张图片

type Info = {
  age: number,
  sex: string,
  hasHouse: boolean
}
type I = Info["age"] //number
const r1: I = 1
// const r2: I = '1' //不能将类型“string”分配给类型“number”。

索引类型本身就是一个类型,因此我们可以完全使用联合、 或其他类型的类型:keyof

type II = Info["age" | "sex"]
const r3: II = 1
const r4: II = '1'

下面这段代码keyof Info将age,sex等组成Info[“age” | “sex” | “hasHouse” ]等。所以最终类型为number | string等

type I = Info[keyof Info] //number | string | boolean
const r1: I = 1
const r2: I = '1'
const r3: I = true
// const r4: I = {}不能将类型“{}”分配给类型“I”。

如下这段代码先定义了一个字符联合类型AgeOrSex ,然后使用索引访问Info[AgeOrSex]可以得出number | string类型。

type AgeOrSex = 'age' | 'sex'
type II = Info[AgeOrSex] //number | string 
let r4: II = 1
let r5: II = '1'
// let r6: II = true 不能将类型“boolean”分配给类型“II”。

如下代码,定义了一个对象数组,想通过该数组定义一个类型I,但是不能直接将该数组赋值给该类型,所以需要使用typeof关键字。并且借助了索引访问数组中的每一个元素。同时推断出类型

const myArray = [
  { name: 'zq', age: 1 },
  { name: 'qw', age: 2 },
  { name: 'rwq', age: 3 },
]
// I = {name:string,age:number}
type I = typeof myArray[number] //索引访问数组中的每一个元素
const p: I = {
  name: "zq",
  age: 12
}

注意下面代码,类型最为number

type Age = typeof myArray[number]["age"]
const age: Age = 1
type I = typeof myArray[number] //索引访问数组中的每一个元素
type Age2 = I["age"]
const a1: Age2 = 1

请注意下面两个代码:如下,定义了一个常量key,值为age。但是在I[]key中使用的是索引访问,需要的是类型作为索引值,但是这里的key是值,所以需要使用typeof 转换。

const key = 'age'
type Age3 = I[key] //报错

修改代码如下解决问题

const key = 'age'
type Age3 = I[typeof key]

type key = 'age'
type Age3 = I[key]

条件类型

interface Animal {
  live(): void
}
interface Dog extends Animal {
  woof(): void;
}

//type Example1 = number
type Example1 = Dog extends Animal ? number : string
// type Example2 =string
type Example2 = RegExp extends Animal ? number : string

定义如下几个重载函数,如果一个库必须在整个 API 中一遍又一遍地做出同样的选择,这就变得很麻烦了。
我们必须创建三个重载:当我们确定类型时,每种情况一个重载(一个用于,一个用于),一个用于最一般的情况(采用 )。对于可以处理的每个新类型,重载的数量呈指数级增长。

interface IdLabel {
  id: number
}
interface NameLabel {
  name: string
}
function createLabel(id: number): IdLabel
function createLabel(name: string): NameLabel
function createLabel(idorname: number | string): IdLabel | NameLabel
function createLabel(idorname: number | string): IdLabel | NameLabel {
  throw ''
}

通过使用条件类型可以缩小上诉多个重载函数的问题

interface IdLabel {
  id: number
}
interface NameLabel {
  name: string
}
//类型可能为number 或string 或 number | string
type IdOrName<T extends number | string> = T extends number ? IdLabel : NameLabel

function createLabel<T extends number | string>(IdOrName: T): IdOrName<T> {
  throw ''
}
createLabel("zq")
createLabel(111)
createLabel(Math.random() > 0.5 ? 1 : '1') //返回类型 IdLabel | NameLabel

条件类型约束

如下代码:只会取执行T["message"]返回的类型,如果不存在希望返回一个never类型该如何处理。

// type Message = T["message"] 不存在message

type Message<T extends { message: string }> = T["message"]

interface Email {
  message: string
}

type EmailMessage = Message<Email>

修改如下

type Message<T> = T extends { message: string } ? T["message"] : never

interface Email {
  message: string
}

// type EmailMessage = string
type EmailMessage = Message<Email> //具有message属性返回索引访问的类型
const em: EmailMessage = '1'

interface Dog {
  bark(): void
}
// type DogMessage = never
type DogMessage = Message<Dog>
// const dm: DogMessage = 1 不能将类型“number”分配给类型“never”。 可以断言为as never
type Flatten<T> = T extends any[] ? T[number] : T //number为数组元素的类型T[0]

//string
type Str = Flatten<string[]> //string[]数组属于any[],
// number
type Num = Flatten<number>

条件类型内进行推理

条件类型为我们提供了一种使用 infer 关键字从 true 分支中与之进行比较的类型中进行推断的方法。

// 函数返回值是是什么类型,Return就是什么类型
type GetReturnType<T> = T extends (...args: never[]) => infer Return ? Return : never

//type Str = string
type Str = GetReturnType<() => string> //函数返回值是string,Return类型为string
// type Num = number
type Num = GetReturnType<(n: number) => number>
// type Bol = boolean[]
type Bol = GetReturnType<() => boolean[]>
// type s = never
type s = GetReturnType<string> //string不属于函数类型
const ns: s = '1' as never

当从具有多个调用签名的类型(如重载函数的类型)进行推断时,将从 最后一个 签名进行推断(这也许是最宽松的万能情况)。无法基于参数类型列表执行重载决议。

type GetReturnType<T> = T extends (...args: never[]) => infer Return ? Return : never
function SN(s: string): string
function SN(n: number): number
function SN(sn: string | number): string | number //从这里开始推断
function SN(sn: string | number): string | number {
  return Math.random() > 0.5 ? 'hello' : 666
}
type StringOrNumber = GetReturnType<typeof SN>
const sn: StringOrNumber = 1 //const sn: string | number

分布式条件类型

Typescript笔记_第34张图片

type ToArray<T> = T extends any ? T[] : never
// type StrArrOrNumArr = string[] | number[]
type StrArrOrNumArr = ToArray<string | number>

const r12: StrArrOrNumArr = [1, 2, 3]

下面这段代码是非分布式条件类型的一个例子

type ToArray<T> = [T] extends [any] ? T[] : never
// type StrArrOrNumArr =(string | number)[]
type StrArrOrNumArr = ToArray<string | number>
const r12: StrArrOrNumArr = [1, "1"]

类属性

如果在一个类中定义了成员变量,但是每次初始化的操作就会给出报错提示。
在这里插入图片描述
可以在定义的时候初始化0

class Point {
  x: number = 0;
  y: number = 0
}

const p = new Point()
p.x = 1;
p.y = 1
console.log(p.x, p.y)

也可以使用构造器初始化

class Point {
  x: number;
  y: number
  constructor() {
    this.x = 0;
    this.y = 0
  }
}

在 TypeScript 中,! 符号是一个非空断言操作符,它可以告诉 TypeScript 编译器一个值不是 null 或 undefined。这样子写也可也解决报错提示信息。问题就是如果忘记赋值就会出现问题。

class Animal {
  name!: string
}

readonly

当一个类的成员变量被设置为只读属性的时候,那么该值不能被实例方法或实例的时候直接修改。但是可以构造函数可以直接修改只读属性,因为构造函数的作用是初始化用的。

class OKGreeter {
  readonly name: string = 'world'
  constructor(str: string) {
    this.name = str //正确
  }
  setName() {
    // this.name = '你好' 错误
  }
}

let p2 = new OKGreeter("hello")
// p2.name = '你还哦' 错误

构造器

类的构造函数可以对类的成员进行实例化,也可以在实例化的时候传入参数,或指定默认值。

class Point {
  x: number;
  y: number
  constructor(x: number = 0, y: number = 0) {
    this.x = x
    this.y = y
  }
}

let p = new Point()
console.log(p.x, p.y)
let p2 = new Point(10, 20)
console.log(p2.x, p2.y)

构造函数不能有类型参数,构造函数不能返回类型注释。

class Point<T> {
  // 这是错误的,构造函数不能有类型参数
  constructor<T>(x: T, y: T) {
    // ...
  }
}

class Point {
  // 这也是错误的,构造函数不能返回类型注释
  constructor(x: number, y: number): Point {
    // ...
    return this;
  }
}

super()的调用

如果一个类是另一个类的子类,那么必须在该类的构造函数第一句调用super()语句

class Base {
  k = 4;
}
 
class Derived extends Base {
  constructor() {
    // Prints a wrong value in ES5; throws exception in ES6
    console.log(this.k);
'super' must be called before accessing 'this' in the constructor of a derived class.
    super();
  }
}

方法

在类中定义一个方法,并在方法中打印输出此时的this,发现它指向了当前对象Point

class Point {
  x: number = 0;
  y: number = 0

  scale(n: number): void {
    console.log(this)
    this.x = n;
    this.y = n
  }
  speak = () => {
    console.log(this)
  }
}
let p = new Point()
p.scale(10)
console.log(p.x, p.y)

p.speak()
// 输出如下
// Point { x: 0, y: 0, speak: [Function (anonymous)] }
// 10 10
//Point { x: 10, y: 10, speak: [Function (anonymous)] }  

getters和setters

  1. 如果存在get,但没有set,则该属性默认是只读的
class C {
  _length = 0 //自动推断为number型
  get length() {
    return this._length
  }
}
let c = new C()
console.log(c.length)

// c.length = 100  错误
  1. 如果没有指定setter参数的类型,它将从getter的返回类型中推断出来。(ts高版本中可以类型不一致)
class C {
  _length = 0 //自动推断为number型
  get length() {
    return this._length
  }
  set length(value) { //从get返回值中推断类型为number
    this._length = value
  }
}
let c = new C()
console.log(c.length) //0
c.length = 100
console.log(c.length) //100

如下就是一个getter和setter中参数类型可以保持不一致的情况,只需要保持set中参数的类型能够兼容get中的返回值的类型

class Thing {
  _size: number = 0;

  get size(): number {
    return this._size
  }

  set size(value: string | number | boolean) {
    let num = Number(value) //存在转换失败的数据
    if (!Number.isFinite(num)) {
      // 转换失败的进入
      this._size = 0
      return
    }
    this._size = num
  }
}

let t: Thing = new Thing()
console.log(t.size) //0
t.size = 10
console.log(t.size) //10
t.size = '100'
console.log(t.size) //100
t.size = true
console.log(t.size) //1
t.size = '1abs'
console.log(t.size) //0
  1. 访问器和设置其必须有相同的成员可见性,即访问权限public,private等

类中的索引签名

class MyClass {
  // 在类中声明联合类型时,函数类型必须用()包起
  [s: string]: boolean | ((s: string) => boolean)

  hasAge = true
  // age=12 错误

  check(s: string): boolean {
    return this[s] as boolean
  }
}

在类中,你也可以使用索引签名,但是需要注意一些限制。

  1. 索引签名必须是public的,不能是private或protected的。
  2. 索引签名不能和类的其他属性或方法冲突。
  3. 索引签名只能有一个,不能同时定义字符串和数字类型的索引签名。

类继承-implements子句

使用implements关键字可以实现一个类实现某个接口的要求

interface Pingable {
  pina(): void
}

class Sonar implements Pingable {
  // 必须实现接口中的方法
  pina(): void {
    console.log("Sonal")
  }
  speak() {
    console.log("说说看")
    this.pina()
  }
}

implements子句只是一种检查,用来确保类可以被当作接口类型来使用。它不会改变类或它的方法的类型。一个常见的错误是认为implements子句会改变类的类型,但实际上不会!

interface Checkable { 
  check(s: string): boolean //认为形参为string,继承的时候就一定为string是错误的
}
class NameChecker implements Checkable {
  // 不完全受接口定义中的类型约束,只需要保持兼容即可
  // 可以返回number类型,但是需要兼容具有string所以设计为联合类型
  check(s: string | number) {
    if (typeof s === 'string') {
      return s.toLowerCase() == 'ok'
    }
    return true
  }
}

如果存在可选参数的时候,但是在类中没有实现该参数,类不会为该参数创建内存空间。

interface A {
  x: number,
  y?: number
}

class C implements A {
  x = 100
}
let c = new C()
console.log(c.x)
// console.log(c.y) 错误,不存在y这一属性

extends子句

使用extends关键字可以让一个类继承某一个类的属性或方法。

class Animal {
  move() {
    console.log("动物会跑")
  }
}
class Dog extends Animal {
  woof(n: number) {
    for (let i = 1; i <= n; i++) {
      console.log(`狗叫了${i}`)
    }
  }
}

let dog: Dog = new Dog()

dog.move()
dog.woof(3)
//动物会跑
//狗叫了1次
//狗叫了2次
//狗叫了3次

重写方法

如果在上诉代码中重写继承的方法,那么在调用的时候,就会先调用被重写的方法。

  move() {
    console.log("狗会跑")
  }
// 狗会跑
// 狗叫了1次
// 狗叫了2次
// 狗叫了3次

派生类中重写的方法必须兼容基类中的方法。如下代码,如果将str属性中的?去掉就会报错

class Base {
  greet() {
    console.log("Hello World!")
  }
}

class Derived extends Base {
  greet(str?: string) {
    if (str === undefined) {
      super.greet() //调用父类的方法
    } else {
      console.log(str.toUpperCase())
    }
  }
}

let d = new Derived()
d.greet()
d.greet("hello")

如下这段代码,将d2定义为基类,然后将派生类赋值给基类的类型是允许的。但是在调用的时候需要注意:TypeScript会根据变量的静态类型(也就是声明时的类型)来决定调用哪个方法。

let d2: Base = d
d2.greet() //调用基类的方法

重载和重写的区别

方法重载是指在同一个类或接口中,定义多个同名但参数不同的方法,以适应不同的调用情况。TypeScript中的方法重载需要定义多个签名(重载签名),以及一个兼容所有签名的实现(实现签名).

class Logger {
  // 方法重载的签名
  log(message: string): void;
  log(message: string, module: string): void;

  // 方法重载的实现
  log(message: string, module?: string): void {
    if (module) {
      console.log(`[${module}] ${message}`);
    } else {
      console.log(message);
    }
  }
}

let logger = new Logger();
logger.log("Hello"); // 调用第一个签名
logger.log("Hello", "App"); // 调用第二个签名

方法重写是指在子类中,定义一个和父类同名同参数的方法,以覆盖父类的行为。TypeScript中的方法重写不需要定义签名,只需要定义实现。

class Animal {
  // 父类的方法
  speak() {
    console.log("Animal speaks");
  }
}

class Dog extends Animal {
  // 子类重写父类的方法
  speak() {
    console.log("Dog barks");
  }
}

let animal = new Animal();
animal.speak(); // 调用父类的方法

let dog = new Dog();
dog.speak(); // 调用子类重写的方法

初始化顺序

了解基类和派生类的初始化顺序。

  • 基类的字段先被初始化
  • 基类的构造函数先运行
  • 派生类的字段后被初始化
  • 派生类的构造函数后运行
class Base {
  name = "base"
  constructor() {
    console.log(`我的名字是:${this.name}`)
  }
}
class Derived extends Base {
  name = 'Derived ' //被重写了
  constructor() {
    super()
    console.log(`我的名字是:${this.name}`)
  }
}

let d = new Derived()
// 我的名字是:base
// 我的名字是:Derived 

继承内置类型

在ES6中,下面的代码可以从ts完全过渡到js中。

class MsgError extends Error {
  constructor(s: string) {
    super(s)
  }
  speak() {
    return this.message //未报错
  }
}

let msg = new MsgError('hello')
console.log(msg.speak())

当修改为ES5的时候,再次执行js文件的时候,就会给出错误提示。这个时候如果想js文件可以运行成功,就需要在构造函数中手动设置原型。这句话的作用是设置this对象的原型对象为MsgError.prototype。这样做的目的是为了让this对象能够正确地使用MsgError类的方法和属性,以及instanceof运算符。 如果不使用Object.setPrototypeOf(),那么this对象的原型对象会默认为Error.prototype,而不是MsgError.prototype。这样会导致this对象无法访问MsgError类定义的方法和属性,以及instanceof运算符会返回错误的结果。

Object.setPrototypeOf(this, MsgError.prototype)

msg instanceof MsgError //如果设置了this的原型就为true否则为false

public

在JavaScript及typescript中没有友元类这一概念,即一个类中的成员不添加任何访问权限的时候默认为public

class Animal {
  speak() { //默认权限public
    console.log("animal")
  }
}

class Dog extends Animal {
  constructor() {
    super()
    this.speak() //可以调用继承的父类方法
  }
}

let p = new Animal()
p.speak()

protected

protected成员仅在当前类及其子类可见。但是在实例对象中无法被访问

class Base {
  protected words() {
    return 'hello'
  }
  public speak() {
    console.log(this.words()) //类中可以访问受保护的成员
  }
}

class Son extends Base {
  say() {
    console.log(this.words()) //子类可以调用父类中的受保护的成员
  }
}

let s = new Son()
// s.words() 属性“words”受保护,只能在类“Base”及其子类中访问。

private

当一个属性被设置为私有权限的时候,那么该属性只能被当前类所访问,不能被其子类或实例对象所访问。

class Base {
  private x = 0
  printX() {
    console.log(this.x) //正确
  }
}

class Derived extends Base {
  showX() {
    // console.log(this.x) 错误
  }
}

let b = new Base()
// b.x 错误

typescript中允许跨实例的私有访问。跨实例的私有访问是指同一个类的不同实例,可以访问对方的私有成员(方法和属性)

class A {
  private x: number;
  constructor(n: number) {
    this.x = n
  }
  public show(other: A) {
    console.log(other.x, this.x)
  }
}

let a1 = new A(10)
let a2 = new A(100)
a1.show(a2) //100 10
a2.show(a1) //10 100

静态成员

使用静态名称需要注意,特殊的静态名不安全,避免使用。typescript中没有静态类的概念。

class Base {
  static x = 10
  static printX() {
    console.log(Base.x)
  }
}
Base.printX()

类中static区块

class Base {
  static #y = 10
  printY() {
    console.log(Base.#y)
  }
  static {
    // 可以访问带#号的成员
    Base.#y = 100
  }
}

// Base.#y 错误,类外无法被访问

注意private staticstatic # 定义的私有静态成员区别。在转换为JavaScript的时候,使用#定义的私有静态字段会被添加到类本身上,而不是添加到类的原型上。因此,在子类中调用父类的静态方法时,this.#y是不存在的。

class Base {
  private static x = 10
  static #y = 100
  static printY() {
    // 静态方法中,this指代类本身,非类实例
    console.log(this.x) //能够在子类继承的方法调用中被使用
    console.log(this.#y) //会报错
  }
}

class Son extends Base { }
Son.printY()

泛型类

class Base<Type> {
  params: Type //不确定具体是什么类型不能在这里初始化
  constructor(props: Type) {
    this.params = props
  }
}

// 隐式推断为string
let b = new Base('hello')
// 显示指定为string
let b2: Base<string> = new Base('hello')
// 或
let b3 = new Base<string>('hello')

在类中不能为一个静态成员指定泛型类型
在这里插入图片描述

类运行中的this

class MyClass {
  name = 'myclass'
  print() {
    return this.name
  }
}

let c = new MyClass()

let obj = {
  name: "obj",
  print: c.print
}

console.log(obj.print()) //obj

如果将类中的print代码修改如下,那么最终打印就是myclass。理由如下:这是因为箭头函数的this关键字是在定义时就绑定的,而不是在调用时动态绑定的。

箭头函数是一种特殊的函数,它没有自己的this,而是继承了外层作用域的this。箭头函数的this关键字是在定义时就确定的,它不会随着调用方式的不同而改变。

例如,在上面的代码中,当我们定义print = () => { return this.name }时,this就是指向MyClass的实例,因为它继承了类中的this。因此,无论我们怎么调用这个箭头函数,它的this都不会变,都是指向MyClass的实例。

所以,当我们调用obj.print()时,即使我们把箭头函数赋值给了obj对象的属性,但是它的this还是指向原来的MyClass的实例,因此返回的结果就是"myclass"。

如果我们把箭头函数改成普通函数,那么结果就不一样了。普通函数的this关键字是在调用时动态绑定的,它会根据调用方式的不同而改变。

  print = () => {
    return this.name
  }

this类型

class Box {
  content: string = ''
  set(value: string) {
    this.content = value
    return this
  }
}

class ClearableBox extends Box {
  clear() {
    // 继承父类的属性
    this.content = 'ClearableBox '
  }
}

let b: Box = new Box()
console.log(b.set('hello')) //谁调用this指向谁:Box

let c: ClearableBox = new ClearableBox()
console.log(c.set('hello')) // ClearableBox

注意下面这段代码:一个基类box一个派生类DerivedBox ,并且分别实例化。derived 是派生类的实例化对象,可以调用继承的方法sameAs,但是在这里传入了一个基类作为参数。需要注意在函数中参数的定义格式other: this。other为box实例对象,但是类型为this,而this又指向了derived ,因此导致了在other中无otherContent属性报错。

class Box {
  content: string = ''
  sameAs(other: this) {
    return other.content === this.content
  }
}
class DerivedBox extends Box {
  otherContent: string = 'DerivedBox'
}
let box = new Box()
let derived = new DerivedBox()
// derived.sameAs(box) 报错

参数属性

用于将构造函数参数转换为具有相同名称和值的类属性。 这些属性称为参数属性

class Person {
  name: string; // 定义一个属性
  constructor(name: string) {
    this.name = name; // 初始化属性
  }
}

等价于
class Person {
  constructor(public name: string) {} // 参数属性
}

类表达式

const Dog = class <Type>{
  name: Type
  constructor(value: Type) {
    this.name = value
  }
}

let dog = new Dog('狗')
console.log(dog.name);

抽象类和成员

抽象方法只能存在于抽象类中,且不能被实现。必须由其派生类实现具体的函数体功能。抽象类不能被实例化。

abstract class Base {
  abstract getName(): string

  print() {
    console.log(this.getName())
  }
}

class Derived extends Base {
  getName(): string {
    return 'Hello'
  }
}

let d = new Derived()
console.log(d.getName())
d.print()

抽象构造签名

当尝试将一个抽象类的类型赋予一个变量并实例化的时候就会报错。那么该如何解决。
在这里插入图片描述

function fn20(ctor: new () => Base) {
  let instance = new ctor()
  instance.print()
}
fn20(Derived)
// fn20(Base) 错误 抽象类不能传递作为参数

类之间的关系

当两个类体几乎一样,类中的属性和类型一致的时候,可以相互替换。注意:需要保持new Point2()兼容Point1的属性。也就是说Point1里有的属性Point2中必须有,相反Point2中存在的属性Point1可以没有

class Point1 {
  x = 1;
  y = 1
}
class Point2 {
  x = 1;
  y = 2;
}

let p: Point1 = new Point2()

下面这段代码中,两个类并没有直接的表明继承关系,但是根据类名我们可以明确谁是基类或子类。
在typescript中,即使没有显式继承,类之间的子类型关系也存在

class Person {
  name: string = ''
  age: number = 0
}

class Employee {
  name: string = 'zq'
  age: number = 40
  salary: number = 100000
}

let per: Person = new Employee()

模块

TS特点的ES模块语法

导出两个类型

export type Dog = {
  name: string
}

export type Cat = {
  name: string
}

引入并使用导出的类型

//导入的时候type只能修饰类型,其中type都可以省略
import type { Dog, Cat } from "./特定的导出";
//或
import { type Dog, type Cat } from "./特定的导出"

type Animal = Dog | Cat

你可能感兴趣的:(typescript,笔记,javascript)