初始TS(二)

148 阅读7分钟

由于上周比较忙,其实这篇文章在上周已经完成了80%了,迟迟没有补充,所以现在才发出来。上期是讲到了TS日常类型中函数中的一些类型注解,这期继续后面的内容。

正文

对象类型

在前面的基础上,我们知道了如何给变量声明类型,其实对象类型也是差不多的,但是要注意,给对象定义类型并不是定义整体的类型,而是要给每个属性都定义好类型。定义好后,对象的元素也只能有类型中的那几个。

比如:

const obj:{name:string,age:number} = {
    name: "Bob",
    age: 19
}
const obj2:{name:string,age:number} = {
    name:"shang",
    age: 10sex: "boy
} // 这种写法就是错误的,对象的属性里不能包含sex,因为在类型中没有定义sex

不仅不能多,也不能少

说到这,其实有办法可以多也可以少,就是可选属性和任意属性

可选属性和任意属性

  1. 可选属性

    可选属性就是可以选择加上这个属性,也可以不要这个属性,语法就是在这个属性后面加上一个问号就行了

    比如:

    let xm:{name:string, age:number, height?:number} = {
        name: "zhangsan",
        age: 19
    }
    let xh:{name:string, age:number, height?:number} = {
        name: "xh",
        age: 19,
        height: 178
    }
    

    上面两种写法都是正确的

  2. 任意属性

    let obj:{ 
        name:string,
        [propName:string]:any
    } = {
        name: "xm",
        age: 18
    }
    

    任意属性就是在类型约束中加上[propName:string]:type,这样的话就可以在对象中随意加上想要的属性了。

    注意:propName 只是个形参,你可以叫任意名字,一般后面的type需要包含你已有的类型还要有你想要加的属性的类型,比如也可以这样写

    let obj:{ 
        name:string,
        [propName:string]:string|number
    } = {
        name: "xm",
        age: 18
    }
    

    这样写会让你的obj更准确,更不容易在运行时出错

联合类型

定义联合类型

你可能会看到的第一种组合类型的方法是联合类型。联合类型是由两种或多种其他类型组成的类型,表示可能是这些类型中的任何一种的值。我们将这些类型中的每一种都称为联合的成员。

在类型中间加上|就是联合类型,表示值可以是这两种类型的其中一种,当然可以随意切换

let val:number|string = 1
val = "1"

使用联合类型

在使用联合类型的时候,需要注意几个地方

我来举个例子吧:

function fn(a:number|string) {
    a.substring() 
    //报错
}

上面这种情况呢就是,ts编译器不能确认a的类型,所以不能使用只有在string类型有的函数,因为它可能是number 但是可以使用两个类型都有的方法,比如toString()

还有一种解决方案就是缩小联合 ,使用typeof

function fn(a:number|string) {
    if(typeof a == "string") {
        a.substring()
    } else if(typeof a == 'number') {
        a.toFixed(2)
    }
}

类型别名

我们一直通过直接在类型注释中编写对象类型和联合类型来使用它们。这很方便,但通常希望多次使用同一个类型并用一个名称引用它。

类型别名正是这样的 - 任何类型的名称。类型别名的语法是:

type <typeName> = 类型

你可以使用类型别名来为任何类型命名,其实就是相当于给类型声明一个变量名

type MyNum = number
type MyStr = string
// 对象
type MyObj = {
    name: string
}
// 函数
type MyFn = () => number
// 数组
type MyArr = number[]
// 元组
type MyCu = [number, string]

// 直接当成类型来使用就可以了

let num:myNum = 1
let obj:MyObj = {name:"xm"}
……

接口

关于接口,相信学过java的人并不陌生

接口声明是命名对象类型的另一种方式

interface MyObj{
    name:string,
    age:number
}
let obj:MyObj = {
    name:"zhangsan",
    age:19
}

接口的继承

可以使用extends关键字继承已有的接口

比如,一些共有的属性可以定义在一个接口中,然后在别的接口定义特殊的属性

interface Animal {
    name:string,
    age:number
}
interface Dog extends Animal {
    run:() => void
}
interface Fish extends Animal {
    swim:() => void
} 

注意:这样实现后,在对象中包括继承而来的属性全部要有

const dog:Dog = {
    name: "旺财",
    age: 2,
    run: () => console.log("跑步")
}

同名接口会自动合并

当自己声明了两个相同名字的接口时,它会自动合并。也可以认为是给第一次接口的声明添加属性。

interface Person {
    name:string
}
interface Person {
    age:number
}
const xm:Person = {
    name:"xiaoming",
    age: 19
}

上面的接口会合并为

interface Person{
    name:string,
    age:number
}

类型别名和接口的区别

一、定义方式和语法

  • type(类型别名)
    • 使用type关键字来定义。例如,定义一个联合类型可以这样写:type Status = "success" | "fail"。它可以用来给各种类型起一个别名,包括基本类型、联合类型、交叉类型、函数类型等。
    • 函数类型可以定义为type Add = (a: number, b: number) => number;,这种定义方式简洁明了,直接描述了函数的参数类型和返回值类型。
  • interface(接口)
    • 通过interface关键字来定义。例如,interface User {name: string; age: number;},它主要用于定义对象的形状,即对象有哪些属性,每个属性是什么类型。
    • 接口也可以用于函数类型的定义,但语法稍有不同。例如,interface Calculate { (a: number, b: number): number; }

二、扩展方式

  • type
    • 联合类型扩展是通过|操作符。例如,type NewStatus = Status | "pending",将新的类型值添加到已有的联合类型中。
    • 交叉类型扩展是通过&操作符。比如,type ComplexType = A & B,可以把两个类型AB的属性合并到一个新类型中。
  • interface
    • 接口扩展是通过extends关键字。例如,interface AdminUser extends User {role: string;}AdminUser接口继承了User接口的所有属性,并且添加了新的role属性。

三、实现和使用场景差异

  • type
    • 适用于定义联合类型、交叉类型等复杂类型组合,以及简单的类型别名。例如,在处理一些复杂的函数参数类型或者返回值类型的组合时,type非常有用。当你需要给一个已经存在的复杂类型起一个更易读的名字时,也可以使用type
  • interface
    • 主要用于对象形状的定义,并且在类的实现或者接口继承场景下使用得较多。在面向对象编程风格比较强的代码中,接口可以很好地定义对象之间的契约关系,使得代码的可读性和可维护性更高。比如,在定义一个组件的属性接口或者服务的接口时,interface是一个很好的选择。

四、重复定义的处理

  • type
    • 不允许重复定义同名的type。如果出现同名的type定义,TypeScript编译器会报错。
  • interface
    • 同名的interface定义会自动合并。例如,在不同的模块或者代码位置定义了同名的接口,TypeScript会将它们的属性合并起来,这个特性在代码拆分和合并场景下很方便。

类型断言

有时候TS编译器无法确认值类型的信息 比如:

function fn(a:number|string) {
    a.substring()
}

这种情况的话使用类型断言可以让ts编译器不要检测这行代码 就比如,下面这段代码不会报错,但是在运行时会报错

function fn(a:number|string) {
    (a as string).substring()
}
fn(1)

注意:类型断⾔只能欺骗ts编译器,让他不报错,⽆法避免项⽬运⾏时的错误,所以使⽤断⾔要谨慎

在类型断言里有个值得注意的点:any类型可以断言为任意类型,任意类型也可以断言为any类型

// 任意类型断言为any类型
let num:number = 1
(num as any).length

// any类型断言为任意类型
let num:any = 5
console.log((num as number).length) //报错

非空断言

TypeScript 还具有一种特殊的语法,可以在不进行任何显式检查的情况下从类型中删除 null 和 undefined。在任何表达式之后写 ! 实际上是一个类型断言,该值不是 null 或 undefined

type func = () => void
function fn(foo:func|null):void {
    let bar = foo!() // 把null这个类型去除
    let bar2 = foo() // 报错
}

双重断言(不推荐使用)

interface Girl{
name:string,
cost:()=>void
}
interface Boy{
name:string,
make:()=>void
}
function fn(obj:Girl){
obj as any as Boy
}

不推荐使用,这里我就不过多描述了

元组类型

元组类型其实就是在数组中含有不同类型元素的数组

const c:[number, string] = [1, 'string']

如果想要添加值,添加的值必须是这两个的联合类型(number|string)

type cu = [number,string]
const c:cu = [1,'1']
c.push('1') // ok
c.push(2) // ok

c.push(true) // 报错