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)