TS数据类型
笔记来源:coderwhy课程
JavaScript 中已经有一些基本类型可用:boolean、 bigint、 null、number、 string、 symbol 和 undefined,它们都可以在接口中使用。TypeScript 将此列表扩展为更多的内容,例如 any (允许任何类型)、[unknown](https://www.typescriptlang.org/play#example/unknown-and-never) (确保使用此类型的人声明类型是什么)、 [never](这种类型不可能发生)和 void (返回 undefined 或没有返回值的函数)。
构建类型有两种语法: 接口和类型。 常用 interface。当需要特定功能时使用 type 。
支持JS数据类型
```TypeScript
// 数字 布尔 字符串类型 ---这些用法和JS一样
let num: number = 123.3
let flag: boolean = true
const name = 'lgl'
// 数组类型 1.数组中存放的类型[] 2.Array<数组中存放的类型> (泛型写法)
let names: string[] = ["aaa", "bbb", "ccc"]
let nums: Array<number> = [111, 222, 333.1];
// 对象类型
//1.直接object--info.name,info.age等报错,不能获取数据和设置数据
const info: object = {
name: "xdh",
age: 18,
};
//2.使用interface type等,或者写全(若补充不全也报错)
let info:{
name:string,
age:number
} = {
name: "xdh",
age: 18,
}
// null undefined类型
const n1: null = null;
const n2: undefined = undefined;
// Symbol类型:Symbol函数返回的是不同值
const title1:symbol = Symbol("title")
const title2:symbol = Symbol('title')
const info = {
[title1]: "程序员",
[title2]: "老师"
}
//函数类型 定义参数类型,定义返回类型
// 1.定义对象类型
type LyricType = {
time: number
text: string
}
// 2.歌词解析工具 传入参数lyric为string,返回一个数组,每一个项是对象
function parseLyric(lyric: string): LyricType[] {
const lyrics: LyricType[] = []
lyrics.push({ time: 1111, text: "天空想要下雨" })
return lyrics
}
const lyricInfos = parseLyric("fdafdafdafa")
for (const item of lyricInfos) {
console.log(item.time, item.text)
}
//可选类型:对象类型也可以指定哪些属性可选,在属性后面添加?
// 可以对象类型和函数类型结合使用
type PointType = {
x: number
y: number
z?: number
}
function printCoordinate(point: PointType) {
console.log("x坐标:", point.x)
console.log("y坐标:", point.y)
}
```
TS特有特性
-
any类型
在 TypeScript 中,
any类型可以被用来表示任何类型,包括原始类型、对象类型以及函数类型。使用
any类型时,编译器将不会对该类型进行任何类型检查,允许在编译时和运行时对其进行任何操作,因此被称为“逃逸舱”类型。应该在必要的时候才使用
any类型,尽可能使用更具体的类型来表示代码中的数据类型。在使用any类型时,可以通过类型断言等方式尽可能减少其使用范围,并在运行时进行必要的类型检查以保证代码的健壮性。// any类型就表示不限制标识符的任意类型, 并且可以在该标识符上面进行任意的操作(在TypeScript中回到JavaScript中) let id: any = "aaaa" id = "bbbb" id = 123 console.log(id.length) id = { name: "why", level: 99 } // 定义数组 const infos: any[] = ["abc", 123, {}, []] -
unknown类型
unknown类型是TS中比较特殊的一种类型,用于描述类型不确定的变量和
any类型类似,但unknown类型的值上做任何事情都不合法let foo: unknown = "aaa" foo = 123 // unknown类型默认情况下在上面进行任意的操作都是非法的 // 要求必须进行类型的校验(缩小), 才能根据缩小之后的类型, 进行对应的操作 if (typeof foo === "string") { // 类型缩小 console.log(foo.length, foo.split(" ")) } export {} -
void类型
void通常用来指定一个函数是没有返回值的,那么它的返回值就是void类型
当基于上下文的类型推导推导出返回类型为void的时候,并不会强制函数一定不能返回内容
// 1.在TS中如果一个函数没有任何的返回值, 那么返回值的类型就是void类型(可以显式指定,也可以不写) // 2.如果返回值是void类型, 那么我们也可以返回undefined(TS编译器允许这样做而已) function sum(num1: number, num2: number): void { console.log(num1 + num2) // return 123 错误的做法 } // 应用场景: 用来指定函数类型的返回值是void type LyricInfoType = { time: number, text: string } // parseLyric函数的数据类型: (lyric: string) => LyricInfoType[] function parseLyric(lyric: string): LyricInfoType[] { const lyricInfos: LyricInfoType[] = [] // 解析 return lyricInfos } // parseLyric => 函数/对象 //foo的类型是FooType->函数类型,没有返回值 type FooType = () => void const foo: FooType = () => {} // 举个例子:(涉及函数的类型问题, 后续还会详细讲解) // 1.定义要求传入的函数的类型 type ExecFnType = (...args: any[]) => void // 2.定义一个函数, 并且接收的参数也是一个函数, 而且这个函数的类型必须是ExecFnType function delayExecFn(fn: ExecFnType) { setTimeout(() => { fn("why", 18) }, 1000); } // 3.执行上面函数, 并且传入一个匿名函数 delayExecFn((name, age) => { console.log(name, age) }) export {} -
never类型
never表示永远不会发生值得类型,比如一个函数:如果一个函数中是一个死循环或者一个异常,这个函数不会返回东西,此时可以用
never类型// 一. 实际开发中只有进行类型推导时, 可能会自动推导出来是never类型, 但是很少使用它 // 1.一个函数是死循环 // function foo(): never { // // while(true) { // // console.log("-----") // // } // throw new Error("1233") // } // foo() // 2.解析歌词的工具 function parseLyric() { return [] } // 二. 封装框架/工具库的时候可以使用一下never // 其他时候在扩展工具的时候, 对于一些没有处理的case, 可以直接报错 function handleMessage(message: string | number | boolean) { switch (typeof message) { case "string": console.log(message.length) break case "number": console.log(message) break case "boolean": console.log(Number(message)) break default: const check: never = message } } handleMessage("aaaa") handleMessage(1234) // 另外同事调用这个函数 handleMessage(true) export {} -
tuple类型
元组类型和数组的区别:
数组中通常建议存放相同类型的元素,不同类型的元素不推荐放在数组中。
元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型;
tuple通常可以作为返回的值,在使用的时候会非常方便。(例如useState的hooks)// 保存我的个人信息: why 18 1.88 // 1.使用数组类型 // 不合适: 数组中最好存放相同的数据类型, 获取值之后不能明确的知道对应的数据类型 const info1: any[] = ["why", 18, 1.88] const value = info1[2] console.log() // 2.使用对象类型(最多) const info2 = { name: "why", age: 18, height: 1.88 } // 3.使用元组类型 // 元组数据结构中可以存放不同的数据类型, 取出来的item也是有明确的类型 const info3: [string, number, number] = ["why", 18, 1.88] const value2 = info3[2] // 在函数中使用元组类型是最多的(函数的返回值) function useState(initialState: number): [number, (newValue: number) => void] { let stateValue = initialState function setValue(newValue: number) { stateValue = newValue } return [stateValue, setValue] } const [count, setCount] = useState(10) console.log(count) setCount(100) export {} -
联合类型和交叉类型
TS允许我们使用多种运算符,从现有类型中构建新类型
-
联合类型
Union Type-
联合类型是由两种或者多种其他类型组成的类型 -
表示可以是这些类型中的任何一个值 -
联合类型中的每一个类型被称之为联合成员 -
使用的时候要进行**类型缩小**
// 1.联合类型的基本使用 // let foo: number | string = "abc" // foo = 123 // // 使用的时候要特别的小心 // if (typeof foo === "string") { // console.log(foo.length) // } // 2.举个栗子: 打印id function printID(id: number | string) { console.log("您的ID:", id) // 类型缩小 if (typeof id === "string") { console.log(id.length) } else { console.log(id) } } printID("abc") printID(123) -
-
交叉类型
-
相关知识 类型别名:type interface
-
给**对象类型**起一个别名--复用,可在多个地方使用 -
type是有个`=` interface不是定义,没有`=`,接口是声明的方式,直接interface 别名 {} -
区别如下: 类型别名和接口非常相似,在定义**对象类型**时,大多时候可以任意选择使用 接口中几乎所有特性都几乎可以在type中使用
// 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 { kouhao: string } const ikun1: IKun = { kouhao: "你干嘛, 哎呦", name: "kobe", age: 30 } // 4.interface可以被类实现(TS面向对象时候再讲) // class Person implements IPerson { // } // 总结: 如果是非对象类型的定义使用type, 如果是对象类型的声明那么使用interface export {} -
-
-
-
交叉类型标识需要满足多个类型的条件 -
交叉类型使用`&`符号 -
交叉类型其实是一个never类型,在开发中,**通常是对对象类型进行交叉的**// 交叉类型: 两种(多种)类型要同时满足 type NewType = number & string // 没有意义 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") } } -
类型断言as和非空类型断言
-
类型断言
as有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions)。
比如我们通过 document.getElementById,TypeScript只知道该函数会返回 HTMLElement ,但并不知道它具体的类型:
// 获取DOM元素 <img class="img"/> // const imgEl = document.querySelector(".img") // if (imgEl !== null) { // 类型缩小 // imgEl.src = "xxx" // imgEl.alt = "yyy" // } // 使用类型断言,在获取的时候直接确定获取的dom元素是图片元素 const imgEl = document.querySelector(".img") as HTMLImageElement imgEl.src = "xxx" imgEl.alt = "yyy"TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换:
// 类型断言的规则: 断言只能断言成更加具体的类型, 或者 不太具体(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 {} -
非空类型断言
!// 定义接口 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 {}
-
-
字面量类型和类型缩小
-
字面量类型
// 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类型 //因为我们的对象在进行字面量推理的时候,info其实是一个 {url: string, method: string},所以我们没办法将一个 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 {} -
类型缩小
- 什么是类型缩小呢?
类型缩小的英文是 Type Narrowing(也有人翻译成类型收窄);
我们可以通过类似于 typeof padding === "number" 的判断语句,来改变TypeScript的执行路径;
在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为 缩小( Narrowing );
而我们编写的 typeof padding === "number 可以称之为 类型保护(type guards);
-
常见的类型保护有如下几种:
typeof
平等缩小(比如===、!==)
instanceof
in
... 等等
-
typeof
在 TypeScript 中,检查返回的值
typeof是一种类型保护: 因为 TypeScript 对如何
typeof操作不同的值进行编码。// 1.typeof: 使用的最多 function printID(id: number | string) { if (typeof id === "string") { console.log(id.length, id.split(" ")) } else { console.log(id) } } -
平等缩小
我们可以使用
Switch或者相等的一些运算符来表达相等性(比如===,!==,==, and!=):// 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("下:", "角色向下移动") } -
instanceof
JavaScript 有一个运算符来检查一个值是否是另一个值的“实例”:
// 3. instanceof: 传入一个日期, 打印日期 function printDate(date: string | Date) { if (date instanceof Date) { console.log(date.getTime()) } else { console.log(date) } //typeof Date是object // if (typeof date === "string") { // console.log(date) // } else { // console.log(date.getTime()) // } } -
in操作符
Javascript 有一个运算符,用于确定对象是否具有带名称的属性:
in运算符 如果指定的属性在指定的对象或其原型链中,则
in运算符返回true;// 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)
-
-
函数类型
-
函数的类型
◼ 在JavaScript开发中,函数是重要的组成部分,并且函数可以作为一等公民(可以作为参数,也可以作为返回值进行传递)。
◼ 那么在使用函数的过程中,函数是否也可以有自己的类型呢?
function foo(arg: number): number { return 123 }//声明的写法 // foo本身也是一个标识符(对象), 也应该有自己的类型 const bar: any = (arg: number): number => { return 123 }//表达式的写法 function delayExecFn(fn) { }◼ 我们可以编写函数类型的表达式(Function Type Expressions),来表示函数类型;
// 方案一: 函数类型表达式 function type expression // 格式: (参数列表) => 返回值,函数列表必须先写形参名字,再赋类型 type BarType = (num1: number) => number const bar: BarType = (arg: number): number => { return 123 } export {} -
函数类型解析
在上面的语法中
(num1: number, num2: number) => void,代表的就是一个函数类型: 接收两个参数的函数:num1和num2,并且都是number类型;
并且这个函数是没有返回值的,所以是void;
type CalcType = (num1: number, num2: number) => number // 1.函数的定义 function calc(calcFn: CalcType) { const num1 = 10 const num2 = 20 const res = calcFn(num1, num2) console.log(res) } // 2.函数的调用 function sum(num1: number, num2: number) { return num1 + num2 } function foo(num1: number) { return num1 } calc(sum) calc(foo) function mul(num1: number, num2: number) { return num1 * num2 } calc(mul) // 3.使用匿名函数 calc(function(num1, num2) { return num1 - num2 }) export {}注意:在某些语言中,可能参数名称num1和num2是可以省略,但是TypeScript是不可以的。
// TypeScript对于传入的函数类型的多余的参数会被忽略掉(the extra arguments are simply ignored.) type CalcType = (num1: number, num2: number) => number function calc(calcFn: CalcType) { calcFn(10, 20) } calc(function(num) { return 123 }) // forEach栗子: const names = ["abc", "cba", "nba"] names.forEach(function(item) { console.log(item.length) }) // TS对于很多类型的检测报不报错, 取决于它的内部规则 // TS版本在不断更新: 在进行合理的类型检测的情况, 让ts同时更好用(好用和类型检测之间找到一个平衡) // 举一个栗子: interface IPerson { name: string age: number } // typescript github issue, 成员 const p = { name: "why", age: 18, height: 1.88, address: "广州市" } const info: IPerson = p export {} -
调用签名(Call Signatures)
在 JavaScript 中,函数除了可以被调用,(函数也是个对象)自己也是可以有属性值的。
然而前面讲到的函数类型表达式并不能支持声明属性;
如果我们想描述一个带有属性的函数,我们可以在一个对象类型中写一个调用签名(call signature);
// 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.name = "aaa" bar.age = 18 bar(123) // 开发中如何选择: // 1.如果只是描述函数类型本身(函数可以被调用), 使用函数类型表达式(Function Type Expressions) // 2.如果在描述函数作为对象可以被调用, 同时也有其他属性时, 使用函数调用签名(Call Signatures) export {}注意这个语法跟函数类型表达式稍有不同,在参数列表和返回的类型之间用的是
:而不是=>。 -
构造签名
JavaScript 函数也可以使用
new操作符调用,当被调用的时候,TypeScript 会认为这是一个构造函数(constructors),因为他们会产生一个新对象。 你可以写一个构造签名( Construct Signatures ),方法是在调用签名前面加一个
new关键词;class Person { } interface ICTORPerson { //构造前面 new (): Person } function factory(fn: ICTORPerson) { const f = new fn() return f } factory(Person) -
参数的可选类型
我们可以指定某个参数是可选的:
◼ 这个时候这个参数y依然是有类型的,它是什么类型呢? number | undefined
◼ 另外可选类型需要在必传参数的后面
// y就是一个可选参数,调用的时候可以传递也可以不传递 // 可选参数类型是什么? number | undefined 联合类型 function foo(x: number, y?: number) { if (y !== undefined) { console.log(y + 10) } } foo(10) foo(10, 20) export {} -
默认参数
◼ 从ES6开始,JavaScript是支持默认参数的,TypeScript也是支持默认参数的:
◼ 这个时候y的类型其实是 undefined 和 number 类型的联合。
// 函数的参数可以有默认值 // 1.有默认值的情况下, 参数的类型注解可以省略 // 2.有默认值的参数, 是可以接收一个undefined的值 function foo(x: number, y = 100) { console.log(y + 10) } foo(10) foo(10, undefined) foo(10, 55) export {} -
剩余参数
function foo(...args: (string | number)[]) { } foo(123, 321) foo("abc", 111, "cba") -
函数的重载(了解)
在TypeScript中,我们可以去编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用;
// 需求: 只能将两个数字/两个字符串进行相加 // 案例分析: any实现 // function add(arg1, arg2) { // return arg1 + arg2 // } // add(10, 20) // add("abc", "cba") // add({aaa: "aaa"}, 123) // 1.实现两个函数 // function add1(num1: number, num2: number) { // return num1 + num2 // } // function add2(str1: string, str2: string) { // return str1 + str2 // } // add1(10, 20) // add2("abc", "cba") // 2.错误的做法: 联合类型是不可以 // function add(arg1: number|string, arg2: number|string) { // return arg1 + arg2 // } // 3.TypeScript中函数的重载写法 // 3.1.先编写重载签名 function add(arg1: number, arg2: number): number function add(arg1: string, arg2: string): string // 3.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 {}一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现;
// 1.普通的实现 // function getLength(arg) { // return arg.length // } // 2.函数的重载 // function getLength(arg: string): number // function getLength(arg: any[]): number // function getLength(arg) { // return arg.length // } // 3.联合类型实现(可以使用联合类型实现的情况, 尽量使用联合类型) // function getLength(arg: string | any[]) { // return arg.length // } // 4.对象类型实现 function getLength(arg: { length: number }) { return arg.length } getLength("aaaaa") getLength(["abc", "cba", "nba"]) getLength({ name: "why", length: 100 }) -
可推导的this类型
TypeScript在编译时,认为我们的
this是可以正确去使用的: 这是因为在没有指定this的情况,
this默认情况下是any类型的;// 在没有对TS进行特殊配置的情况下, this是any类型 // 1.对象中的函数中的this const obj = { name: "why", studying: function() { // 默认情况下, this是any类型 console.log(this.name.length, "studying") } } obj.studying() // obj.studying.call({}) // 2.普通的函数 function foo() { console.log(this) } export {}this的相关工具
function foo(this: { name: string }, info: {name: string}) { console.log(this, info) } type FooType = typeof foo // 1.ThisParameterType: 获取FooType类型中this的类型 type FooThisType = ThisParameterType<FooType> // 2.OmitOmitThisParameter: 删除this参数类型, 剩余的函数类型 type PureFooType = OmitThisParameter<FooType> // 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 {}
-