TypeScript 学习笔记 环境安装-类型注解-语法细节-类-接口-泛型

文章目录

  • TypeScript 学习笔记
    • 概述
      • TypeScript 开发环境搭建
    • 类型注解
      • 类型推断
    • 数据类型
      • JS的7个原始类型
      • Array数组
      • object、Object 和 {}
        • 可选属性 ? 和 可选链运算符?.
      • function函数
      • TS类型: any类型 | unknow类型
      • TS类型: void类型
      • TS类型:never类型 (几乎不用)
      • TS类型:Tuple(元组)
      • 语法细节
        • 1.联合类型 | 和交叉类型 &
        • 2.type和interface接口作为类型声明
        • 3.类型断言 as 和非空类型断言
        • 4.字面量类型和类型缩小
        • 5.函数的类型和函数签名
          • 函数表达式
          • 函数调用签名:支持声明属性
          • 构造签名(理解)
        • 6.函数的重载和this类型
          • 函数的重载(通用库和工具中使用较多)
          • this类型(很少使用)
          • this相关的内置工具(了解)
        • 7.函数的参数
        • 可选参数
          • 默认参数
          • 剩余参数
    • TypeScript 类的使用
      • 类的特性
      • 抽象类abstract
    • TypeScript对象类型
      • 索引签名
      • 函数签名
    • TypeScript接口
      • 类实现接口 implements
      • 抽象类和接口的区别
      • 特殊:严格字面量赋值检测
    • TypeScript枚举类型
    • 泛型
      • 泛型语法的基本使用
      • 泛型约束和类型条件
        • keyof 和 in
      • TypeScript映射类型(开发中很少用,用于封装组件)
        • 映射类型修饰符

TypeScript 学习笔记

概述

JavaScript的变量类型相当于是动态类型,可以跟随着赋值的改变而类型改变,函数的参数也没有设定类型,所以在定位错误以及安全性上不太够。

说明
1.TS不能被JS解析器直接执行,需要编译成JS执行
2.即使TS编译出错也可以编译成JS

1.TypeScript是什么?

TypeScript 是类型安全的JavaScript(拥有类型的JavaScript超集),JavaScript是弱类型, 很多错误只有在运行时才会被发现,而TypeScript提供了一套静态检测机制, 在编译时就发现错误

2.TypeScript的好处是什么?
1.支持强类型
2.类型注解:通过类型注解来增加编译时静态类型检查
3.增加了特性:泛型、接口、抽象类等

TypeScript 学习笔记 环境安装-类型注解-语法细节-类-接口-泛型_第1张图片

TypeScript 开发环境搭建

安装TS解析器 :解析器使用nodejs写的 —> 先安装nodejs我node已经安装好啦,直接安装TypeScript

1.使用npm全局安装TypeScript

npm i -g typescript

2.检查是否安装成功

tsc --version

3.创建一个ts文件:后缀以ts结尾
使用tsc对ts文件进行编译,进入到该文件的命令行,使用tsc 文件名进行编译

tsc test.ts

TS运行效果查看
1.通过tsc编译TS代码到JS代码
2.在浏览器环境或者node环境下运行JS代码

简化版本

  • 通过webpack 配置本地的TS编译环境和开启一个本地服务,可以直接运行在浏览器上(ts-loader)
  • 通过ts-node库,为TS的运行提供执行环境
# 安装ts-node
npm install ts-node -g
# 按照ts-node依赖包tslib @types/node
npm install tslib @types/node - g
# node上运行js
node xxx.js
# node上运行ts
ts-node xxx.ts

类型注解

类型注解:主动告诉ts是什么类型
语法:变量名:数据类型
说明
1.冒号后面的数据类型被称为类型注解★
2.声明了类型后TS就会进行类型检测

类型推断

类型推断:声明一个变量时,如果有直接赋值,会根据赋值的类型推导出标变量的类型注解。

说明
1.声明变量如果不指定类型,也不赋值,则TS解析器会自动判断变量的类型为any
2.从右到左推断
3.let进行类型推导,推导出来是通用类型
const类型推导,推导出来是字面量类型

const height = 1.88 //1.88为heigth的类型,1.88是字面量类型
const height:1.88 //等价于

数据类型

  • JS数据类型: 7个原始类型 (string,number,boolean,undefined,null,bigint,symbol) + Array + Object
  • TS新增类型: any + unknow +void + never

总结
1.默认情况下 nullundefined 是所有类型的子类型,这两种类型可以赋值给任意类型的变量
4.never类型是任何类型的子类型,也可以赋值给任何类型的变量
2. unknown 是 顶级类型,任何类型的值(never除外)都可以赋值给该类型的变量
3. any 是顶级类型,任何类型的值(never除外)都可以赋值给该类型的变量,也是所有类型的子类型,any相当于关闭类型检测

JS的7个原始类型

let str: string = "jimmy";
let num: number = 24;
let bool: boolean = false;
let u: undefined = undefined;
let n: null = null;
let big: bigint = 100n;
let sym: symbol = Symbol("me"); 

说明
1.默认情况下 nullundefined 是所有类型的子类型。 可以把 null 和 undefined 赋值给其他类型。

如果你在tsconfig.json指定了"strictNullChecks":true ,null 和 undefined 只能赋值给 void 和它们各自的类型。

2.String是JavaScript中字符串包装类

Array数组

//明确指定数组的类型注解写法1
let arr:string[] = ["1","2"];
//明确指定数组的类型注解写法2 泛型写法
let arr2:Array<string> = ["1","2"]

object、Object 和 {}

语法:{属性名:属性值,属性名:属性值....}
说明
1.属性个数名字必须一模一样,不能多不能少
2.分割符分号和逗号可以,换行可以省略分号,因为JS中换行会自动添加分号,最好使用逗号。
3.object、Object 和 {}

  • Object 代表所有拥有 toStringhasOwnProperty 方法的类型,所有原始类型、非原始类型都可以赋给 Object。在严格模式下,null 和 undefined 类型也不能赋给 Object。
  • {}等价于Object
  • object 则表示非原始类型
let info: object = {
      name: 'why',
      age: 18
    }
info = [1,2,3]; //非原始类型
console.log(info);
info = 123; //报错

4.类型不注解,默认为any


以下写法,在实际开发中并不会使用,因为object并不清楚里面的属性是什么类型,获取到对象里的属性会报错。

//不使用
let info: object = {
  name: 'why',
  age: 18
}
console.log(info['age']) //报错

/*
写法1
let info: {
name:string
age:number
}
写法2
let info: {name:string;age:number}
写法3
let info: {name:string,age:number}
*/
let info: {
name:string
age:number
} = {
  name: 'why',
  age: 18
}

可选属性 ? 和 可选链运算符?.

可选属性?
如果某一属性不确定,可以使用?标识该属性可选
语法:{属性名:属性值,属性名?:属性值}

可选链运算符?.
?.用来判断左侧的表达式是否是 null | undefined,如果是则会停止表达式运行,可以减少我们大量的&&运算。

a === null || a === void 0 ? void 0 : a.b;
function getData(data: any){
	let name = data?.row?.name //data && data.row ? data.row.name:undefined
}
//普通写法
function getData(data: any){
	let name;
	if (data && data.row) {
		name = data.row.name
	}
}

比如我们写出a?.b时,
理解1:a ? a.b :undefined
理解2:如果a为null或者undefined,则后面的表达式等于没有,如果存在则获取a.b

访问属性的时候可以用可选链,但是赋值时不可以使用比如: xxx?.xxx=xxx

function函数

function add(x: number, y: number): void {
     x + y;
}

1.匿名函数 - 大多数时候不需要类型注解
当一个函数出现在TS可以确定该函数会被如何调用的地方时,该函数的参数会自动指定类型,最好不要加类型注解

const names = ['abc', 'cba', 'nba']
// 根据上下文环境推导出来的,这个时候可以不添加类型注解
// 上下文中的函数,可以不添加类型注解
names.forEach(function (item,index,arr) {
  console.log(item,index,arr)
})

这是因为TypeScript会根据forEach函数的类型以及数组的类型推断出item的类型;
这个过程称之为上下文类型(contextual typing),因为函数执行的上下文可以帮助确定参数和返回值的类型;

TS类型: any类型 | unknow类型

1.在 TypeScript 中,任何类型都可以被归为 any 类型,因此any 类型也是类型系统的顶级类型。
2.变量如果在声明的时候,未指定其类型,会被识别为任意值类型。
3.unknownany一样,所有类型都可以分配给unknown ,都表示不清楚是类型。

unknown 是 top type (任何类型都可以赋值给unknow类型) , 而 any 既是 top type, 又是 bottom type (任何类型都可赋值给any类型的变量,any类型可以赋值给任何类型) 。

区别 赋值 描述
any any上访问任何属性都是允许的,也允许调用任何方法 any 类型可以赋值给任意类型 any相当于关闭类型检验
unknow 默认情况下unknow类型上不能访问属性和调用方法(执行任何操作?),必须进行类型校验(缩小)才可以执行对应操作 unknow 类型只能赋值给 any 和 unknow 类型 unknow是更加安全的any,会进行类型检测
let foo:unknown = 'aaa'//合法
foo = 123 //合法
console.log(foo);//合法
console.log(foo.length); //非法
//类型缩小
if(typeof foo ==="string"){
console.log(foo.length); //合法
}

TS类型: void类型

当函数没有返回值,则返回void类型

function foo (num1:number,num2:number):void{
    console.log(num1+num2)
    return undefined
    //null也可以
    return null
}
/*
foo的类型是()=>void 表示是一个返回void的函数类型
const类型推导,推导出来是字面量类型
const height = 1.88 //1.88为heigth的类型,1.88是字面量类型
const height:1.88 //等价于
*/
const foo = () =>{}
//等价于
type FooType = () => void
const foo:FooType = ()=>{}

说明
1.可以返回null和undefined,因为默认情况下nullundefined是所有类型的子类型。
2.方法没有返回值将得到undefined,但是需要定义成void类型,而不是undefined类型,否则将报错。
3.当基于上下文的类型推导推导出返回类型为void的时候,并不会强制函数一定不会返回内容。(了解)
TypeScript 学习笔记 环境安装-类型注解-语法细节-类-接口-泛型_第2张图片

let names:Array<string> = ['arr1','arr2']
names.forEach((item,index,arr)=>{
    return 123;//没有意义但是不会报错
})

TS类型:never类型 (几乎不用)

开发中很少使用never类型
never表示永不存在的值
①函数抛出异常,函数不会执行完毕并返回
②函数中执行无限循环的代码,使得程序永远无法运行到函数返回值那一步,永不存在返回。

新版本已经修改,类型推导出来是void

function foo(){
while (true) {}
}
function bar(){
  throw new Error()
}

never的使用场景

//封装工具/框架
function handleMessage(message: string | number ) {
  switch (typeof message) {
    case 'string':
      console.log('string处理方式处理message')
      break
    case 'number':
      console.log('number处理方式处理message')
      break
    default:
      const check: never = message //这一步永远来不到
  }
}

handleMessage('abc')
handleMessage(123)
//另外一个人调用这个函数传入布尔类型,函数定义参数部分添加boolean类型,check:never位置报错,就会发现case少了一个布尔类型

TS类型:Tuple(元组)

元组类型表示一个已知元素数量和类型的数组(固定长度和类型的数组),各元素的类型不必相同。

数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中。如果不同类型放在数组中,取值时并不清楚每个元素的具体类型。

使用元组类型的好处
1.元组数据结构中可以存放不同的数据类型,取出来的item也有明确的类型
2.减少对象key的使用

let x: [string, number] = ['hello', 10];
console.log(x[0])

//类型推导:(string|numbner)[] ,取值并不清楚每一个元素是什么类型
let y = ['hello',10]

使用场景:当函数需要返回多个值的时候

function useState<T>(state: T) {
  // T 泛型
  let currentState = state
  const changeState = (newState: T) => {
    currentState = newState
  }
  const tupple: [T, (newState: T) => void] = [currentState, changeState]
  return tupple //返回值为[参数,函数]
}

const [
  counter, // number
  setCounter // (newState: number) => void
] = useState(10)

setCounter(1000)

const [
  title, // string
  setTitle //  (newState: string) => void
] = useState('abc')

const [
  flag, // boolean
  setFlag //  (newState: boolean) => void
] = useState(true)

语法细节

1.联合类型 | 和交叉类型 &

联合类型:取值可以为多种类型中的一种,使用 | 分隔每个类型
交叉类型: 一般用于多种对象类型取交集 ,使用& 分隔

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven'; // OK
myFavoriteNumber = 7; // OK

说明
1.联合类型中的每一个类型被称之为联合成员(union’s members)
2.通常配合缩小联合一起使用

function printId(id:number|string){
	if(typeof id === 'string'){
	//缩小范围
	}else{
	}
}

交叉类型使用场景

interface Person {
	name:string
	age:number
}
interface Fans {
	name:string
    like:string
}
const Women :Person&Fans = {
    name:"ranran",
    age:18,
    like:'study'
}

2.type和interface接口作为类型声明

type 用于定义类型别名 (type alias)

//使用type声明对象
type IDType = string | number | boolean
//分号和逗号可以
type PointType = {x: number,y: number}
type PointType = {x: number;y: number}

type PointType = {//换行可以省略分号,因为js中换行会自动添加分号
  x: number
  y: number
}

接口可以看成规则,实现接口就必须满足接口设置的规则
语法 interface 接口名

//使用借口来声明对象
interface PointType{
    x:number
    y:number
}
interface PointType{
	z:number
}
//多次声明需要都满足
const obj:myInterface = {//obj对象必须满足接口的规则有name和age
	x:1,
	y:2,
	z:3
}

类型别名与接口的区别

- 相同点 不同点
别名 1.类型别名和接口非常相似,在定义对象类型时,大部分时候可以任意选择使用。
2.接口中几乎所有的特性都可以在type中使用
1.使用范围更广
2.不允许同名声明两次(报错)
接口 1.只能声明对象
2.可以多次声明同一个接口名称,属性叠加

开发常用:其他数据类型常用type,对象常用interface因为可扩展性更强

接口的继承extends

接口支持多继承

interface Person {
	name:string
	age:number
}
interface Fans extends Person{
    like:string
}

3.类型断言 as 和非空类型断言

类型断言:TypeScript 无法获取具体的类型信息,但是开发员自己清楚,就可以显式告诉TS
非空类型断言: !表示在此处告诉编译器,不会为nullundefined

// message -> undefined | string
function pringMessage(message?: string) {
  console.log(message!.length) //显式告诉不为空,如果不这样写会报错,因为message可能为空
}

pringMessage('Hello World')
pringMessage()

4.字面量类型和类型缩小

可以联合多个类型,类似于枚举的作用

//left right center分别是一个字面量
type Alignment = 'left' | 'right' | 'center'
let align:Alignment = //从上述字面量中选择一个

字面量本身就是string类型,所以对象在进行推理时会把options里的属性推理为string类型

type Method = 'GET' | 'POST'
type requestInfo= {
  url: string
  method: Method
}

//其他细节
function request(url: string, method: Method) {}
//报错的情况
const options= {
	url: 'http://www.coderwhy.org/abc',
	method: 'POST' // 类型推导,推导出来 options.method 是 string 类型
 }
request(options.url, options.method) // 第二个参数报错
options.method = '123' // 存在安全隐患 
//解决方案1,优选
const options: requestInfo= {
	url: 'http://www.coderwhy.org/abc',
	method: 'POST'
}
//解决方案2:进行类型断言(但是不方便别人维护时阅读)
request(options.url, options.method as "post") 
//解决方案3:设置options的时候就确定类型
const options:{url:string,methond:'POST'}= {
	url: 'http://www.coderwhy.org/abc',
	method: 'POST' 
 }

//解决方案4:tips写法
const options = {
  url: 'http://www.coderwhy.org/abc',
  method: 'POST'
} as const //把属性变成字面量,仅读

request(options.url, options.method)

类型缩小: 变量有一个确定的类型,在使用时对其类型做更小的类型限制,使得代码更加安全

  • typeof
  • 平等缩小(一些等号相关) instanceof
  • in(用于确定对象是否具有带名称的属性,如果指定的属性在指定的对象或其原型链中,则in 运算符返回true)
// 1. typeof 的类型缩小
type IDType = number | string
function printID(id: IDType) {
  if (typeof id === 'string') {
    console.log(id.toUpperCase())
  } else {
    console.log(id)
  }
}

// 2. 平等的类型缩小 (=== == !== !=/switch)
type Direction = 'left' | 'right' | 'top' | 'bottom'
function printDirection(direction: Direction) {
  //下面这串代码可以省略了
  //1. if 判断
  // if (direction === 'left') {
  //   console.log(direction)
  // } else if() {}
  // 2. switch 判断
  // switch(direction) {
  //   case 'left':
  //     console.log(direction);
  //     break
  //   case ...
  // }
}

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

class Student {
  studying() {}
}

class Teacher {
  teaching() {}
}

function work(p: Student | Teacher) {
  if (p instanceof Student) {
    p.studying()
  } else {
    p.teaching()
  }
}

// 4. in
interface Fish  {
  swimming: () => void
}
interface Dog  {
  running: () => void
}

function walk(animal: Fish | Dog) {
  if ('swimming' in animal) { //不能用.运算符时可以考虑in
    animal.swimming()
  } else {
    animal.running()
  }
}

const fish:Fish  = {
  swimming() {
    console.log('swimming')
  }
} 

5.函数的类型和函数签名

函数表达式

编写函数类型的表达式(Function Type Expressions),来表示函数类型

函数类型表达式:(参数列表) => 函数返回值类型

function calc(
  n1: number,
  n2: number,
  fn: (num1: number, num2: number) => number
) {
  return fn(n1, n2)
}


const result1 = calc(20, 30, function (a1, a2) {
  return a1 + a2
})
console.log(result1) // 50

//这里虽然只传入一个参数,但是不会报错
const result2 = calc(20, 30, function (a1) {
  return a1 //因为只使用了一个参数,所以可以只传入一个参数
})

console.log(result2) // 600

对于传入为函数类型的参数,当实参小于形参时不会报错会自动省略多余形参
可以想象匿名参数forEach,使用时并不是所有形参都传进去了

函数调用签名:支持声明属性

函数本身可以调用,并且函数也是一个对象,是对象就有属性。

格式:(参数列表):返回值类型 用于对象内
作用:描述带谁能够的函数,可以在一个对象类型中写一个签名,签名的作用是表示这是一个函数可以被调用

interface Bar{
  name:string
}
//不会报错
const bar:Bar =(num1:number):number =>{
    return 123
}
//调用时报错此表达式不可调用,因为bar的类型是Bar,并不知道其是一个函数可以被调用
bar()

//函数调用签名
interface Bar{
  name:string
  (num1:number):number
}
const bar:Bar =(num1:number):number =>{
    return 123
}
bar(123)

如果仅描述一个函数可以被调用,使用函数表达式。如果在描述函数作为对象可以被调用,同时也有其他属性时,使用函数调用签名。

构造签名(理解)

JavaScript函数也可以使用new操作符调用,当被调用时,TS会认为这是一个构造函数,因为产生了一个新的对象。

function foo(){
}
//可以new 但是不知道f的类型,会推断f为any
const f = new foo()

//问题:如何表达fn是一个构造函数,不是一个普通函数
function factory(fn){
  return new fn()
}
//解决:使用构造函数签名
class Person{ 
}
interface ICTORPerson{//符合当前规则的函数就是构造函数,可以通过new
  new():Person //new调用后的返回值是Person的示例,相当于这个函数new了之后返回Person示例
}
function factory(fn:ICTORPerson){
  const f = new fn()
  return f
}
factory(Person)

6.函数的重载和this类型

函数的重载(通用库和工具中使用较多)

函数的重载:函数的名称相同,但是参数不同的几个函数,就是函数的重载

实现需求: 如果我们编写了一个add函数,希望可以对字符串和数字类型进行相加
解决办法: 编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用

  //需求:如果我们编写了一个add函数,希望可以对字符串和数字类型进行相加
  //错误写法
  type AddType = number | string
  function add(a1: AddType, a2: AddType) {
     return a1 + a2  //报错AddType和AddType类型不能相加
   }
  /*写法1联合类型的缺点
  1.进行很多逻辑判断
  2.返回值的类型不能确定
  */
  function add(a1: number | string, a2: number | string) {
    if (typeof a1 === 'number' && typeof a2 === 'number') {
      return a1 + a2
    } else if (typeof a1 === 'string' && typeof a2 === 'string') {
      return a1 + a2
    }
  }

实现方法:一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现
调用时根据传入的参数类型决定执行函数体时,执行哪一个函数的重载签名;

注意点:

  1. 重载签名没有函数体卸载通用函数之前
  2. 通用函数不能被调用
//1.2一起构成函数重载
//1.重载签名,不需要函数体
function add(num1: number, num2: number): number
function add(num1: string, num2: string): string
//2.通用的函数实现 ,通用函数不能被直接调用
function add(a1: any, a2: any) :any{
  return a1 + a2 
}

联合类型和函数重载都可以实现一个功能时,优先选择联合类型,联合类型不能实现时再选择函数重载

this类型(很少使用)

没有对js进行特殊配置时,this的默认类型是any

  const info = {
    name: 'why',
    eating() {
      console.log(this.name + ' eating')
    }
  }
  info.eating()
  function foo(){
    console.log(this)
  }
  foo()

tsc --init 初始化配置文件tsconfig.json

当这个参数为true时,TypeScript会根据上下文推导this,但是不能正确选择时,就会报错,需要明确指定this的类型。
指定规则: 函数的第一个参数,参数名必须是this,其他参数从第二个开始传递,编译成js代码是第一个参数会被删除。

//this配置:没有模糊不明确(比如any,所以配置之后上述代码会报错)的this
noImplicitThis:true

//显式指定this的类型,将this作为函数的第一个参数,名字必须是this
type ThisType = { name: string }
function eating(this: ThisType, message: string) {
  console.log(this.name + ' eating', message)
}
const info = {
  name: 'why',
  eating
}
// 显式绑定
eating.call({ name: 'kobe' }, '呵呵呵')
eating.apply({ name: 'james' }, '嘿嘿嘿 ')

this相关的内置工具(了解)

TypeScript提供一些工具类来辅助进行常见的类型转换,这些类可以全局使用。

  • ThisParameterType<>:用于提取一个函数类型Type的this参数类型,没有this参数返回unknown
  • OmitThisParameter<>:移除Type的this参数类型,并返回当前的函数类型
  • ThisType<>:用于绑定上下文的this类型(piana里的getters和action,讲的很奇怪,先记下来)
type ThisType = { name: string }
  function eating(this: ThisType, message: string) {
    console.log(this.name + ' eating', message)
  }

//获取函数eating的类型:type ty = (this: ThisType, message: string) => void
type ty =  typeof eating
//提取出函数里面this的类型type eatingThisType = {name: string;}
type eatingThisType = ThisParameterType<ty>
//type pureEatingType = (message: string) => void
type pureEatingType = OmitThisParameter<ty>

interface IState{
    name:string,
    age:number
}  
interface IStore{
  state:IState,
  eatting?:()=>void,
}
//给store绑定上下文this到IState
const store:IStore & ThisType<IState>  = {
  state:{
    name:"ranran",
    age:18
  },
  eatting:function(){
    console.log(this.name)//如果没绑定获取到的应该是store的this里面没有name属性,需要使用this.state.name获取。当很多函数使用到this.state里面的属性时代码会很冗余,将store的this绑定到IState之后,this就从state里面取name。
  }
}

7.函数的参数

  • 必传参数 -> 有默认值的参数 -> 可选参数

可选参数

使用指定某个参数是可选的,类似于可选属性

//可选参数的类型:number | undefined
//y -> undefined | number
function foo(x: number, y?: number) {
    console.log(x, y)
}
foo(20, 30)
foo(20)
默认参数
  • 有默认值的情况下,类型注解可以省略
  • 有默认值的参数是可以接受undefined类型,不写时相当于传入了undefined,当ts发现接受到undefined就给参数赋默认值(了解即可,使用时认为是指定的,不是联合的)
// 必传参数 -> 有默认值的参数 -> 可选参数
function foo(x: number, y: number = 100) {
  console.log(x, y)
}
//省略类型注解的情况
function foo(x: number, y = 100) {
  console.log(x, y)
}

foo(20) // 20 100
剩余参数

剩余的参数会被封装成一个数组

function sum(initalNum: number, ...nums: number[]) {
  let total = initalNum
  for (const num of nums) {
    total += num
  }
  return total
}

console.log(sum(20, 30))
console.log(sum(20, 30, 40))
console.log(sum(20, 30, 40, 50))

TypeScript 类的使用

类的特性

ES6的class可以看作只是一个语法糖,看成构造函数的另一种写法,所以类可以看成是一个有构造签名的函数。

//案例1
class Point{}
typeof Point //'function'
Point === Poin.prototype.constructor //true
//案例2 类可以看成是一个有构造签名的函数
class Person{};
//表示stor是一个函数类型,并且是一个构造函数
function factory(stor: new()=> void){}
factory(Person)
//案例2的写法2
class Person{};
interface ty{
    new():void
}
//表示有构造签名的
function factory(stor: ty){}
factory(Person)
  • static静态属性(方法)/类属性(方法):不需要创建实例就可以使用,是属于类的通过类名.属性(方法)调用
  • 实例属性(方法):是属于实例化出的这个对象,通过对象.属性(方法)调用
  • 类的所有方法都定义在类的prototype属性上
  • 实例属性除非显式定义在本身(this),其余都是定义在原型上

1.成员属性必须先声明

class Person{
  //1.成员属性必须要先声明
  name:string = ""; //可以类型注解和设置初始化值
  age:number
  constructor(name:string,age:number){
    this.name = name
    this.age = age
  }
}
const p1 = new Person("ranran",18)
const p2 = new Person("biubiu",18)

2. 类的成员修饰符

成员修饰符 描述 范围
public(默认值) 属性为共有属性 任意位置访问和修改
protected 属性为保护属性 此类和其子类中(子类实例也不可以)访问和修改
private 属性为私有属性 只能在此类里访问和修改

3.可见修饰符的构造器语法糖(参数属性)
TypeScript提供特殊的语法参数属性: 可以将构造函数参数转换成一个同名同值的类属性

1.创建声明同名的成员属性
2.可见修饰符修饰成员属性(权限 | readonly)

//完整写法
class Person{
	public name:string; 
	constructor(name:string){
		this.name = name;
	}
}
//简洁写法
class Person{
	constructor( public name:string){
		this.name = name;
	}
}

4.在TypeScript可以使用存取器使私有属性被外界访问

  • get 属性名(){} 表示getter方法,在属性被读取时自动调用
  • set 属性名(){} 表示setter方法,在属性被修改时自动调用
class Person{
	private _name:string; //私有属性:属性前面使用_(习惯约定)如果没有写下划线,在读取/修改该属性时并不知道访问的是属性name还是方法name
	constructor(name:string){
		this._name = name;
	}
	get name(){
		return this._name
	}
	set name(value){
		this._name = value;
	}
}
const per = new Person("ranan");
console.log(per.name);//读取属性自动调用get name(){}
per.name = "biubiu"; //修改属性set name(){}

5.成员修饰符readOnly只读
readonly属性:表示该属性只读不可以修改,和static连用需要放在其后面

class Person{
	name:"ranan" //定义实例属性
	readonly age:15 //定义只读属性
	call(){} //定义实例方法
}

6.TS类型检测:鸭子类型
TS类型检测:鸭子类型(走得像鸭子声音像鸭子就是鸭子)
鸭子类型:只关心属性和行为是否一样,并不关心是不是具体对应的类型

//情况1:不会被报错
makeArea({getArea:()=>{}})
//情况2
class A {
  name:"ranran"
}
class B{
   name:"ranran"
}
const a:A = new B();

抽象类abstract

抽象类:多个方法有共同方法,但是实现方式有差别。可以将同名方法抽离到父类,由子类去实现抽象方法

  • abstract修饰类 - 抽象类
    • 抽象类不能被实例化,只能被继承
    • 抽象类中可以有抽象方法,也可以有普通方法
  • abstract修饰方法 - 抽象方法
    • 抽象方法只能被定义在抽象类中,子类必须重写抽象类
    • 抽象方法没有方法体
//多态的体现:父类的引用指向子类
function makeArea(shape: Shape) {
  return shape.getArea()
}

abstract class Shape { //抽象类
  abstract getArea() //抽象方法
}

class Rectangle extends Shape {
  private width: number
  private height: number
  constructor(width: number, height: number) {
    super()
    this.width = width
    this.height = height
  }

  getArea() {
    return this.width * this.height
  }
}

class Circle extends Shape {
  private r: number
  constructor(r: number) {
    super()
    this.r = r
  }

  getArea() {
    return this.r * this.r * 3.14
  }
}

const rectangle = new Rectangle(20, 30)
//此时makeArea方法的形参是Shape的子类
console.log(makeArea(rectangle))

const circle = new Circle(10)
console.log(makeArea(circle))

TypeScript对象类型

  • ?可选属性
  • readonly 只读属性
  class Person{
    name?:"ranran"
    readonly age:15
  }

索引签名

索引签名:可以通过索引访问[标识符自己起:通过什么索引访问]:索引访问之后返回值的类型
使用场景:不知道具体的实现是什么样的,但是知道具备某种特性

说明

  • 一个索引签名属性的类型必须是string或者number其中一个
  • 数字类型索引的类型必须是字符串类型索引的子类型

原因:所有数字类型访问索引时最终都是转换为string类型访问的,如果同时设置了,数字类型0拿到的类型需要符合字符串类型’0’拿到的类型

//案例1
interface IndexLanguage {
	[index:number]:string //通过number索引访问,访问之后返回string类型数据
	//这里定义的属性也得满足索引签名的要求
	//如果需要两种类型,需要分开写(语法不支持)
	 [index:string]:string 
}
function getInfo():IndexLanguage {} //不知道具体的实现是什么样的,但是知道具备某种特性
const frontLanguage = getInfo()

//案例2
interface IPerson {
  //告知通过索引获取到的数据类型是什么样
  [index:string]: string
}
//报错原因 ["biu","ranran"]通过字面量创建的Array实例 =>数组中自带了很多其他属性比如name.forEach返回值是Function不是string
const name:IPerson = ["biu","ranran"]

函数签名

interface来定义对象中普通的属性和方法的,实际上它也可以用来定义函数类型(除非特别的情况,推荐使用类型别名来定义函数)

// type CalcFn = (n1: number, n2: number) => number

// 可调用接口
interface CalcFn {
  (n1: number, n2: number): number
}

function calc(num1: number, num2: number, calcFunc: CalcFn) {
  return calcFunc(num1, num2)
}

const add: CalcFn = (num1, num2) => num1 + num2
calc(20, 30, add)

TypeScript接口

类实现接口 implements

接口可以看成规则,对类进行约束

说明

  • 接口只定义对象的结构,而不考虑实际值
  • 在接口中所有的方法都是抽象方法

定义类时可以使类去实现一个接口(满足接口定义的规则),一个子类只能有一个父类,但是可以实现多个接口

interface myInterface{ //接口定义必须有name和sayHello
	name:string,
	sayHello():void; 
}
class MyClass implements myInterface{
	name:stringconstructor(name:string){
		this.name = name;
	}
	sayHello(){} //类的实现
}

抽象类和接口的区别

区别 抽象类 接口
继承 抽象类是类,一个类只能继承一个抽象类 一个类可以实现多个接口
方法 可以是普通方法和抽象方法 全是抽象方法
子类 子类必须覆盖抽象类的抽象方法 子类必须遵守接口定义的规则,接口中的所有东西都得有

抽象类是对类本质的抽象,表达的是 is a 的关系。比如:male is a Human。抽象类包含并实现子类的通用特性
接口是对行为的抽象,表达的是 like a 的关系。比如:Baoma like a plane(它有飞的功能一样可以飞),但其本质上 is a Car。接口的核心是定义行为,即实现类可以做什么

特殊:严格字面量赋值检测

第一次创建的对象字面量被表示为新鲜的,对于新鲜的字面量会进行严格的类型检测(必须完全满足类型的要求,不能有多余的属性)。
当一个新的对象字面量分配给一个变量或传递给一个非空目标类型的参数时,对象字面量指定不存在的属性是错误的。

name: string
   age: number
}
  
const p: IPerson = { //第一次创建新鲜的,被严格的检测
    name: 'why',
    age: 18,
    height: 1.88//报错,Iperson接口没有height
}
  
const info = { //第一次创建新鲜的,被严格的检测
    name: 'why',
    age: 18,
    height: 1.88 
}
// freshness 不是新鲜的不严格检测
const p2: IPerson = info // 不报错  

TypeScript枚举类型

格式:enum 枚举类型标识符 {}

enum Color {//没设置值,初始值默认从0开始按顺序自增,可以理解为数组下标
  RED,
  PINK,
  BLUE="blue",//设置初始值
}
const pink: Color = Color.PINK;
console.log(pink); // 1

泛型

泛型语法的基本使用

泛型指是在定义函数或者类时并不知道类型,在被使用时才知道数据的类型

泛型可以指定多个,定义泛型<格式类>
T被称为类型变量(type variable),T作为参数泛化成具体类型的作用叫做类型参数化。

  • 泛型也可以指定默认值
//定义泛型T,定义的位置一般在标识符的后面
function fn <T> (a:T):T{
	return a;
}
fn(10); //此时T是number,利用类型推断 const
fn <string>("hello") //显式指定T是string,为了方便阅读,最好显式指定

interface Inter{//定义接口
	length:number;
} 
function fn<T = string>(a:T):number{//这里的泛型必须继承Inter接口
	return a.length;
} 

泛型类的使用案例

class Point<T> {
  x: T
  y: T
  z: T
  constructor(x: T, y: T, z: T) {
    this.x = x
    this.y = y
    this.z = z
  }
}

const p1 = new Point('1.33.2', '2.22.3', '4.22.1')
const p2 = new Point<string>('1.33.2', '2.22.3', '4.22.1')
const p3: Point<string> = new Point('1.33.2', '2.22.3', '4.22.1')

const names1: string[] = ['abc', 'cba', 'nba']
const names2: Array<string> = ['abc', 'cba', 'nba'] 

泛型约束和类型条件

对泛型的类型进行约束

interface Inter{//定义接口
	length:number;
} 
//写法1:这里的泛型继承Inter接口
//T相当于是一个变量,用于记录本次调用的类型,所以在函数执行周期中,会保留参数的类型
function fn<T extends Inter>(a:T):number{
	return a;
} 
const f = fn("aaaa") //写法1便于TS推断f的类型为字符串
//写法2:
function fn(a:Inter):Inter{//这里的泛型必须继承Inter接口
	return a; 
} 
const f = fn("aaaa") //写法2会导致形参a的原始类型丢失,推导f的类型为Inter

keyof 和 in

  • keyof 类型 接受一个对象类型作为参数,返回该对象属性名组成的字面量联合类型
  • in的右侧一般会跟一个联合类型,使用in操作符可以对该联合类型进行迭代。 其作用类似JS中的for...in或者for...of
//keyof使用案例
type Dog = { name: string; age: number;  };
type D = keyof Dog; //type D = "name" | "age"

//in的使用案例
type Animals = 'pig' | 'cat' | 'dog'​
type animals = {
    [key in Animals]: string
}
// type animals = {
//     pig: string; //第一次迭代
//     cat: string; //第二次迭代
//     dog: string; //第三次迭代
// }

keyof any返回联合类型string | number | symbol

补充:遇见索引签名时,keyof会直接返回索引的类型。索引类型为string时,keyof返回的类型是string | number,这是因为数字类型索引最终访问时也会被转换为字符串索引类型。

  type Dog = {  [y:number]: number  };
  type dog = keyof Dog;  //type dog = number
  ​
  type Doggy = {  [y:string]: boolean };
  type doggy = keyof Doggy; //type doggy = string | number

使用场景:与extends关键字结合使用,对对象的属性做限定。

在泛型中使用的案例

// k extends  "name"| "age" 这里的extends表示k是不是其中一个联合类型
function getObjectProperty<O,K extends keyof O>(obj:O,key:K){
    return obj[key]
} 
const info = {
    name:"ranran",
    age:18
}

getObjectProperty(info,"name")

TypeScript映射类型(开发中很少用,用于封装组件)

映射类型:可以理解为将一个类型映射成一个新的类型

使用场景:一个类型需要另一个类型,但是又不想拷贝(映射)一份,可以考虑使用映射类型。

可以将映射类型想象成一个函数,函数的作用就是拷贝(映射)类型

//映射类型MapPerson
//<>定义泛型,接收需要拷贝的类型  
type MapPerson<Type> = {
    //[index:number]:any //索引签名写法
    //Property自定义的标识符, keyof Type表示联合类型,in从联合类型中依次取值赋值给Property
    [Property in keyof Type]:Type[Property]
}
interface Iperson{
    name:string
    age:number
}

type NewPerson = MapPerson<Iperson> //使用映射类型拷贝Iperson类型

映射类型修饰符

可能使用的两个类型

  • readonliy,设置属性只读
  • 设置属性可选

可以通过前缀-或者+删除或添加这些修饰符,默认使用+

//映射类型MapPerson
//将所有属性映射为可选属性
type MapPerson<Type> = {
   //+readonly [Property in keyof Type]:Type[Property]
   -readonly [Property in keyof Type] -?:Type[Property]
}

interface Iperson{
    name:string
    age:number
}

type NewPerson = MapPerson<Iperson>
/*
type NewPerson = {
    readonly name: string;
    readonly age: number;
}
*/

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