TypeScript学习04-TypeScript的语法细节

4.1、联合类型

TypeScript的类型系统允许我们使用多种运算符,从现有类型中构建新类型

比如:number、string、boolean

const foo:number|string|boolean='abc'|'123'|true //这就是个新类型

我们来使用第一种组合类型的方法:联合类型(Union Type)

  • 联合类型是由两个或者多个其他类型组成的类型;
  • 表示可以是这些类型中的任何一个值;
  • 联合类型中的每一个类型被称之为联合成员(union’s members);
function printId(id:number|string){
    console.log("你的id是:",id)
}
printId(10)
printId("abc")

4.2、使用联合类型

传入给一个联合类型的值是非常简单的:只要保证是联合类型中的某一个类型的值即可

  • 但是我们拿到这个值之后,我们应该如何使用它呢?因为它可能是任何一种类型。
  • 比如我们拿到的值可能是string或者number,我们就不能对其调用string上的一些方法;

那么我们怎么处理这样的问题呢?

  • 我们需要使用缩小(narrow)联合(后续我们还会专门讲解缩小相关的功能);
  • TypeScript可以根据我们缩小的代码结构,推断出更加具体的类型;
function printId(id:number|string){
    if(typeOf id==='string'){
        console.log('你的id是:',id.toUpperCase())
    }else{
        //确定id是number类型
        console.log("你的id是",id)
    }
}

4.3、类型别名

在前面,我们通过在类型注解中编写 对象类型 和 联合类型,但是当我们想要多次在其他地方使用时,就要编写多次。

比如我们可以给对象类型起一个别名:

type Point={
    x:number
    y:number
}

function printPoint(point:Point){
    console.log(point.x,point.y)
}

function sumPoint(point:Point){
    console.log(point.x+point.y)
}

printPoint({x:30,y:20})
sumPoint({x:20,y:30})
type ID=number|string

function printId(id:ID){
    console.log("您的id:",id)
}

4.4、接口的声明

在前面我们可以通过type来声明一个对象类型

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

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

interface PointType{
    x:number
    y:number
    z?:number
}
    
function printCoordinate(point: PointType){
    
}

那么它们有什么区别呢?

类型别名和接口非常的相似,在定义对象类型时,大部分时候,你可以任意选择使用

接口的几乎所有特性都可以在type中使用,(后续我们还会再学习interface的很多特性)

// 1、区别一:type类型的使用范围更加的广
type MyNumber=number

type IDType=number|string

//2、区别二:在声明对象时,interface可以多次声明
//2.1、type不允许两个相同的别名同时存在  ------会报错的
type PointType1={
    x:number
    y:number
}

type PointType1={
    z?:number
}

//2.2、interface可以多次声明同一个接口名称
//第一次声明
interface PointType2{
    x:number
    y:number
}
//第二次声明
interface PointType2{
    z?:number 
}
    
//两次声明都是有效的      
const point:PointType2={
    x:100
    y:200
    z:300
}
    
//3、interface支持继承的
interface IPerson{
    name:string
    age:number
}

interface IKun extends IPerson{
    //name:string  //没有必要在写一遍,所以用继承
    //age:number
    kouhao:string
}
    
const ikun1:IKun={
    kouhao:"你干嘛,哎哟喂!!!"
    name:"caixukun"
    age:30
}    

//4、interface 可以被类实现(TS的面向对象在讲这个东西)
class Person implements IPerson{
    
}    

总结:

Type的应用范围更广、即可以声明我们的对象类型同时也可以声明别的一些数据类型、比如、联合类型、或者number类型、或者函数类型

但interface他只是用来声明对象类型的、但是在声明对象类型的时候,他的这个特性会更多一点,我们可以利用他的特性来减少我们对某些代码更多的一个复用。

所以一般情况再开发中的做法

对于声明一些别的类型的时候我们一般使用type,对于我们声明对象的时候,通常我们直接使用interface

TypeScript只是在我们开发阶段、编译阶段给我们做类型限制而已,最终目的是做类型限制、其实用Type和interface无所谓的,不影响效果,但是从代码拓展性的

声明对象的时候还是用interface比较好

4.5、interface和type区别(简述版)

我们会发现interface和type都可以用来定义对象类型,那么在开发中定义对象类型时,到底选择哪一个呢?

  • 如果是定义非对象类型,通常推荐使用type,比如Direction、Alignment、一些Function;

如果是定义对象类型,那么他们是有区别的:

  • interface 可以重复的对某个接口来定义属性和方法;
  • 而type定义的是别名,别名是不能重复的;
interface IPerson{
    name:string
    running:()=>void    
}

interface IPerson{
    age:number
}
type Person={
    name:string
    runing:()=>void
}
    
//error: Duplicate identifier 'Person'.ts(2300)
type Person={
    age:number
}    

所以,interface可以为现有的接口提供更多的扩展

  • 接口还有很多其他的用法,我们会在后续详细学习

4.6、交叉类型

在前面我们学习了联合类型

  • 联合类型表示多个类型中一个即可
type Alignment='left'|'right'|'center'

还有另外一种类型合并,就是交叉类型(Intersection Types):

  • 交叉类似表示需要满足多个类型的条件;
  • 交叉类型使用 & 符号;

我们来看下面的交叉类型:

type MyType=number&string
  • 表达的含义是number和string要同时满足
  • 但是有同时是一个number又是一个string的值嘛?其实是没有的,所以MyType其实就是一个never类型

4.7、交叉类型的应用

//回顾联合类型
type ID=number| string
const id1:ID="abc"
const id2:ID=123

//交叉类型:两种(多种)类型要同时满足
type NewType=number&string //没有意义 --never类型

interface IKun{
    name:string
    age:string
}

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

//交叉类型的使用场景
const info:IKun & ICoder={
    name:"why"
    age:18
    coding:function(){
        console.log("coding")
    }
}    

4.8、类型断言as

as…把什么作为什么来使用

有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions)。

  • 比如我们通过 document.getElementById,TypeScript只知道该函数会返回 HTMLElement ,但并不知道它具体的类型:
const myEl=document.getElementById("my-image") as HTMLImageElement
myEl.src="图片资料"    

TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换:

const name="coderwhy" as number
const name=("coderwhy" as unknown) as number; //这种从Ts类型检测的角度去看不错,但是这种写法是不可取的

测试代码

// 获取DOM元素 <img class="img"/>
// const imgEl = document.querySelector(".img")
// if (imgEl !== null) { // 类型缩小
//   imgEl.src = "xxx"
//   imgEl.alt = "yyy"
// }

// 使用类型断言
const imgEl = document.querySelector(".img") as HTMLImageElement
imgEl.src = "xxx"
imgEl.alt = "yyy"


// 类型断言的规则: 断言只能断言成更加具体的类型, 或者 不太具体(any/unknown) 类型
const age: number = 18
// 错误的做法
// const age2 = age as string

// TS类型检测来说是正确的, 但是这个代码本身不太正确
// const age3 = age as any
// const age4 = age3 as string
// console.log(age4.split(" "))

export {}

4.9、非空类型断言

当我们编写下面的代码时,在执行ts的编译阶段会报错:

  • 这是因为传入的message有可能是为undefined的,这个时候是不能执行方法的;
function printMessage(message?.string){
    //error TS2532:Object is possibly 'undefined'
    console.log(message.toUpperCase())
}
printMessage("hello")

但是,我们确定传入的参数是有值的,这个时候我们可以使用非空类型断言:

  • 非空断言使用的是 ! ,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测;
function printMessage(message?:string){
    console.log(message!.toUpperCase())
}

测试代码

// 定义接口
interface IPerson {
  name: string
  age: number
  friend?: {
    name: string
  }
}

const info: IPerson = {
  name: "why",
  age: 18
}

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

// 属性赋值:
// 解决方案一: 类型缩小
if (info.friend) {
  info.friend.name = "kobe"
}

// 解决方案二: 非空类型断言(有点危险, 只有确保friend一定有值的情况, 才能使用)
info.friend!.name = "james"

export {}

4.1、字面量类型

除了前面我们所讲过的类型之外,也可以使用字面量类型(literal types):

let message:"hello World"="Hello World"
// Type '"你好啊,李银河"' is not assignable to type '"Hello World"'
message="你好啊,李银河"

那么这样做又有什么意义呢?

  • 默认情况下这么做是没有太大的意义的,但是我们可以将多个类型联合在一起;限定死这个方法的参数只能传这几种情况
type Alignment='left'|'right'|'center'
function changAlign(align:Alignment){
    console.log("修改方向:",align)
}
changeAlign("left")

4.2、字面量类型推导

我们来看下面的代码:

const info={
    url:"https://maochang.top/abc"
    method:"GET"     
}
function request(url:string,method:"GET"|"POST"){
    console.log(url,method)
}
request(info.url,info.method)// --后边会报红--如下

在这里插入图片描述

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

// 解决方案一 采用类型断言的方式
request(info.url,info.method as "GET")

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

// 解决方案三:
const info2 = {
  url: "xxxx",
  method: "post"
} as const

// xxx 本身就是一个string
request(info2.url, info2.method)

测试代码

// 1.字面量类型的基本上
const name: "why" = "why"
let age: 18 = 18

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

// 栗子: 封装请求方法
type MethodType = "get" | "post"
function request(url: string, method: MethodType) {
}

request("http://codercba.com/api/aaa", "post")

// TS细节
// 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)

export {}

4.3、类型缩小

什么是类型缩小呢?

  • 类型缩小的英文是 Type Narrowing(也有人翻译成类型收窄);
  • 我们可以通过类似于typeof padding === “number” 的判断语句,来改变TypeScript的执行路径;
  • 在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为缩小(Narrowing);
  • 而我们编写的 typeof padding === "number" 可以称之为 类型保护(type guards)

常见的类型保护有如下几种:

  • typeof

  • 平等缩小(比如===、!==

  • instanceof

  • in

  • 等等…

测试代码

// 1.typeof: 使用的最多
function printID(id: number | string) {
  if (typeof id === "string") {
    console.log(id.length, id.split(" "))
  } else {
    console.log(id)
  }
}


// 2.平等缩小
// 我们可以使用Switch或者相等的一些运算符来表达相等性(比如===, !==, ==, and != ):
// ===/!==: 方向的类型判断 “字面量判断用的比较多” 我们也可以用Switch
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
// JavaScript 有一个运算符来检查一个值是否是另一个值的“实例”:
// 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: 判断是否有某一个属性
// Javascript 有一个运算符,用于确定对象是否具有带名称的属性:in运算符
// 如果指定的属性在指定的对象或其原型链中,则in 运算符返回true;
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)
  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值