02学习TypeScript

1. 联合类型和交叉类型

1.1联合类型(Union Type)

定义:从现有类型中构建新类型,由两个或者多个其他类型组成的类型;表示可以是这些类型中的任何一个值;联合类型中的每一个类型被称之为联合成员(union’s members) ;

// 打印id
function printID(id:number | string){
    console.log("您的ID:",id);

    //类型缩小,确定id是string类型
    if(typeof id === "string"){
        console.log(id.length,id.toUpperCase());
    }else{
    	//确定id是number类型
        console.log(id);
    }
}

printID("abc")
printID(123)

1.2 类型别名

在介绍交叉类型之前,需要介绍类型别名
在类型注解中编写对象类型和联合类型,但是当我们想要多次在其他地方使用时,就要编写多次。

// 类型别名type
type myNumber = number
const age: myNumber = 18

//给ID的类型起个别名
type IDType = number | string
function printID(id: IDType) {
    console.log(id);
}

// 打印坐标
type pointType = { x: number, y: number, z?: number }
function printCoordinate(point: pointType) {
    console.log(point.x, point.y, point.z);
}
printCoordinate({x:10,y:20})

1.3 接口声明

通过type可以用来声明一个对象类型:

type PointType = {
    x: number,
    y: number,
    z?: number
}

另外一种声明对象类型方式就是通过接口来声明:

// 接口:interface
// 声明的方式
interface PointType2 {
    x: number,
    y: number,
    z?: number
}

类型别名和接口非常相似,在定义对象类型时,大部分时候,你可以任意选择使用。接口的几乎所有特性都可以在type 中使用(后续我们还会学习interface的很多特性);
接口声明和别名区别:

// 1.区别一: type类型使用范围更广 接口类型只能用来声明对象
type myNumber = number
type IDType = number | string


// 2.区别二:在声明对象时,interface可以多次声明
// 2.1. type不允许两个相同名称的别名同时存在

// 2.2. interface可以多次声明同一个接口名称
interface PointType2 {
    x: number,
    y: number
}

interface PointType2 {
    z?: number
}

const point: PointType2 = {
    x: 100,
    y: 90
}

// 3.interface支持继承的
interface IAnimal {
    category: string
}

interface ICat extends IAnimal {
    name: string,
    age: number
}

const cat1: ICat = {
    category: "猫",
    name: "花花",
    age: 2
}
// 4.interface可以被类实现(TS面向对象时候再讲)

export { }

总结: 如果是非对象类型的定义使用type,如果是对象类型的声明那么使用interface,扩展更好

1.4 交叉类型

  • 交叉类型表示需要满足多个类型的条件;
  • 交叉类型使用&符号;
// 回顾: 联合类型
type ID = number | string
const id1: ID = "abc"
const id2: ID = 123

// 交叉类型: 两种(多种)类型要同时满足
type NewType = number & string // 没有同时满足是一个number又是一个string的值,所以NewType是一个never类型;
interface IKun {
    name: string
    age: number
}

interface ICoder {
    name: string
    coding: () => void
}

type InfoType = IKun & ICoder

const info: InfoType = {
    name: "why",
    age: 18,
    coding: function () {
        console.log("coding")
    }
}

2. 类型断言和非空断言

2.1 类型断言as

有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions)
比如我们通过document.querySelector(“.img”),TypeScript只知道该函数会返回HTMLElement,但并不知道它具体的类型:

TypeScript只允许类型断言转换为更具体或者不太具体的类型版本

// 使用类型断言,转换为更具体
const imgEl = document.querySelector(".img") as HTMLImageElement
imgEl.src = "xxx"
imgEl.alt = "yyy"

2.2 非空类型断言

非空断言使用的是!,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测;

interface IPerson{
    name:string,
    age:number,
    friend?:{
        name:string
    }
}

const info:IPerson = {
    name:"haha",
    age:10
}

//访问属性: 可选链: ?.
console.log(info.friend?.name);

// 属性赋值不能用可选链
// 解决方案一:类型缩小
if(info.friend){
    info.friend.name = "kobe"
}
// 解决方案二:非空类型断言!(有点危险,, 只有确保friend一定有值的情况, 才能使用)
info.friend!.name = "james"

export {}

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

3.1 字面量类型

将多个字面量类型联合起来

// 将多个字面量类型联合起来 |
type Direction = "left" | "right" | "up" | "down"
const d1: Direction = "left"

对象在进行字面量推理的时候,info其实是一个{url: string, method: string},所以没办法将一个string赋值给一个字面量类型

// const info = {
//   url: "xxxx",
//   method: "post"
// }
// 下面的做法是错误: info.method获取的是string类型,而不是字面量类型
// request(info.url, info.method)

// 解决方案一: info.method进行类型断言
request(info.url, info.method as "post")

// 解决方案二: 直接让info对象类型是一个字面量类型
// const info2: { url: string, method: "post" } = {
//   url: "xxxx",
//   method: "post"
// }
const info2 = {
    url: "xxxx",
    method: "post"
} as const
// xxx 本身就是一个string
request(info2.url, info2.method)

3.2 类型缩小(Type Narrowing)

  • 我们可以通过类似于typeof padding === "number”的判断语句,来改变TypeScript的执行路径;
  • 在给定的执行路径中,可以缩小比声明时更小的类型,这个过程称之为缩小(Narrowing ) ;
  • typeof padding === "number可以称之为类型保护(type guards) ;

常见的类型保护种类:

  • 1.typeof
    // 1.typeof: 使用的最多
    function printID(id: number | string) {
    if (typeof id === "string") {
        console.log(id.length, id.split(" "))
    } else {
        console.log(id)
    }
    }
    
  • 2.平等缩小(比如===、!==):方向的类型判断
    type Direction = "left" | "right" | "up" | "down"
    function switchDirection(direction: Direction) {
    	if (direction === "left") {
        	console.log("左:", "角色向左移动")
    	} else if (direction === "right") {
        	console.log("右:", "角色向右移动")
    	} else if (direction === "up") {
        	console.log("上:", "角色向上移动")
    	} else if (direction === "down") {
       	 	console.log("下:", "角色向下移动")
    	}
    }
    
    
  • // 3. instanceof: 传入一个日期, 打印日期
    function printDate(date: string | Date) {
    	if (date instanceof Date) {
        	console.log(date.getTime())
    	} else {
        	console.log(date)
    	}
    
    	// if (typeof date === "string") {
    	//   console.log(date)
    	// } else {
    	//   console.log(date.getTime())
    	// }
    }
    
  • 4.in: 判断是否有某一个属性
    interface ISwim {
    	swim: () => void
    }
    
    interface IRun {
    	run: () => void
    }
    
    function move(animal: ISwim | IRun) {
    	if ("swim" in animal) {
        	animal.swim()
    	} else if ("run" in animal) {
        	animal.run()
    	}
    }
    
    const fish: ISwim = {
    	swim: function () { }
    }
    
    const dog: IRun = {
    	run: function () { }
    }
    
    move(fish)
    move(dog)
    
  • 等等…

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

4.1 函数类型

// 方案一: 函数类型表达式function type expression
// 格式: 	   (参数列表) 	  => 返回值类型
type BarType = (num1: number) => number //函数类型

const bar:BarType = (arg:number):number=>{
    return  123
}

函数类型练习:由函数外部决定+ - * /运算

//定义函数类型  
type callBackType = (num1: number, num2: number) => number
// 1.函数的定义
function calc(callBackFn: callBackType) {
    const num1 = 10
    const num2 = 20
    const res = callBackFn(num1, num2)
    return res
}


// 2.函数的调用
// 2.1 匿名函数参数不需要写类型,会自动上下文推导
console.log(
    calc(function (num1, num2) {
        return num1 + num2
    })
);
// 2.2 
function add(num1:number,num2:number){
    return num1 + num2
}
console.log("相加:",calc(add));


export { }

TypeScript对于传入的函数类型的多余的参数会被忽略掉(the extra arguments are simply ignored)
原因:大多函数都是匿名函数,比如forEach函数的三个参数大多数情况都用不到,一般用第一个参数item就够了,必须要写全部的三个参数反而影响TS使用体验
只传入一个参数也可以:function foo(num1:number){return num1} calc(foo);
错误的:function foo(num1:string){return num1} calc(foo);

4.2 函数签名

调用签名

函数也是个对象,可以有自己的属性值
前面讲到的函数类型表达式并不能支持声明属性;
如果想描述一个带有属性的函数,可以在一个对象类型中写一个调用签名(call signature) ;
开发中如何选择:

  1. 如果只是描述函数类型本身(函数可以被调用), 使用函数类型表达式(Function Type Expressions)
  2. 如果在描述函数作为对象可以被调用, 同时也有其他属性时, 使用函数调用签名(Call Signatures)
// 1.函数类型表达式
type BarType = (num1: number) => number

// 2.函数的调用签名(从对象的角度来看待这个函数, 也可以有其他属性)
interface IBar {
    name: string
    age: number
    // 函数可以调用: 函数调用签名
    (num1: number): number
}

const bar: IBar = (num1: number): number => {
    return 123
}

bar(123)
bar.name = "aaa"
bar.age = 18

构造签名

class Person {
}

interface ICTORPerson {
  new (): Person
}

function factory(fn: ICTORPerson) {
  const f = new fn()
  return f
}
//传入的是可以new的构造函数和类
factory(Person)

5. 函数的参数

5.1可选参数

指定某个参数可选,可选类型需要在必传参数的后面

// y就是一个可选参数
// 可选参数类型是什么? number | undefined 联合类型
function foo(x: number, y?: number) {
  //y还有可能是undefined,需要类型缩小(收缩)
  if (y !== undefined) {
    console.log(y + 10)
  }
}

foo(10)
foo(10, 20)

export {}

5.2参数的默认值

// 函数的参数可以有默认值
// 1.有默认值的情况下, 参数的类型注解可以省略
// 2.有默认值的参数, 是可以接收一个undefined的值
function foo(x: number, y = 100) {
    console.log(y + 10)
}

foo(10)
foo(10, undefined)
foo(10, 55)

export { }

5.3剩余参数

function foo(...args:(string|number)[]){

}

foo(123,321)
foo('a','b','c',1)

6. 函数的重载和this类型

6.1函数的重载(了解)

实现只能将两个数字/两个字符串进行相加:

  • 在TypeScript中,我们可以去编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用;
  • 一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现;
// 需求: 只能将两个数字/两个字符串进行相加
// TypeScript中函数的重载写法
// 1.先编写重载签名
function add(arg1: number, arg2: number): number
function add(arg1: string, arg2: string): string

// 2.编写通用的函数实现
function add(arg1: any, arg2: any): any {
  return arg1 + arg2
}

add(10, 20)
add("aaa", "bbb")
// 通用函数不能被调用,没有与此调用匹配的重载。
// add({name: "why"}, "aaa")
// add("aaa", 111)

export {}

6.2联合类型和函数重载选择

定义一个函数,可以传入字符串或者数组,获取它们的长度。
可以使用联合类型实现的情况, 尽量使用联合类型
实现方案:

  • 使用联合类型
  • 使用函数重载
  • 使用对象类型(包含Length属性)
//1.联合类型实现(可以使用联合类型实现的情况, 尽量使用联合类型)
/* 
function getLength(arg:string|any[]) {
    return arg.length
} 
*/

// 2.函数的重载
/* 
function getLength(arg:string):number
function getLength(arg:any[]):number

function getLength(arg){
    return arg.length
} 
*/

// 3.对象类型实现
function getLength(arg:{length:number}){
    return arg.length
}
getLength("abc")
getLength(["a",1,3])
getLength({ length: 100 })

export { }

6.3可推导的this类型

王红元(coderwhy)老师的javascript讲解this的文章地址.

函数中this的默认类型

// 在没有对TS进行特殊配置的情况下, this是any类型

// 1.对象中的函数中的this
const obj = {
    name: "why",
    studying: function () {
        // 默认情况下, this是any类型
        console.log(this.name.length, "studying")
    }
}

obj.studying()
//危险的,指定this为{}  undefined.undefined
// obj.studying.call({})


// 2.普通的函数
function foo() {
    console.log(this)
}

export { }

初始化生成配置文件tsconfig.json:tsc --init
在设置配置选项(编译选项compilerOptions, noImplicitThis设置为true, 不允许模糊的this存在)

//tsconfig.json:不能有模糊的this
"noImplicitThis": true

指定this的类型

函数的第一个参数可以根据该函数之后被调用的情况,用于声明this的类型(名词必须叫this) ;
在后续调用函数传入参数时,从第二个参数开始传递的,this参数会在编译后被抹除;

function foo(this: { name: string }, info: { name: string }) {
    console.log(this, info)
}

foo.call({ name: "why" }, { name: "kobe" })

this相关的内置工具

  1. ThisParameterType:
function foo(this: { name: string }, info: { name: string }) {
    console.log(this, info)
}

type FooType = typeof foo

// 1.ThisParameterType: 获取FooType类型中this的类型
type FooThisType = ThisParameterType<FooType>

export { }
  1. OmitThisParameter:
function foo(this: { name: string }, info: { name: string }) {
    console.log(this, info)
}

type FooType = typeof foo
// 2.OmitOmitThisParameter: 删除this参数类型, 剩余的函数类型
type PureFooType = OmitThisParameter<FooType>

export { }
  1. ThisType :
function foo(this: { name: string }, info: { name: string }) {
    console.log(this, info)
}

type FooType = typeof foo

// 3.ThisType: 用于绑定一个上下文的this
interface IState {
    name: string
    age: number
}

interface IStore {
    state: IState
    eating: () => void
    running: () => void
}

const store: IStore & ThisType<IState> = {
    state: {
        name: "why",
        age: 18
    },
    eating: function () {
        console.log(this.name)
    },
    running: function () {
        console.log(this.name)
    }
}

store.eating.call(store.state)


export { }

写在最后

本篇文章到这里就结束了,如果文章对你有用,可以三连支持一下,如果文章中有错误或者说你有更好的见解,欢迎指正~
代码地址: lemonDin/learn_typescript(github.com).

PS:代码和思路来自王红元(coderwhy)老师,在其中做个总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不愿安逸的少少

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值