
TypeScript入门——2.常用类型与语法
类型声明
使用 :
对变量或函数形参,进行类型声明:
语法:let或const 变量: 变量类型
示例:
let a: string // 变量a只能存储字符串
let b: number // 变量b只能存储数值
let c: boolean // 变量c只能存储布尔值
a = 'hello'
// a = 100 // 类型声明.ts:6:1 - error TS2322: Type 'number' is not assignable to type 'string'.
b = 666
// b = '你好' // 类型声明.ts:9:1 - error TS2322: Type 'string' is not assignable to type 'number'.
c = true
// c = 666 // 类型声明.ts:12:1 - error TS2322: Type 'number' is not assignable to type 'boolean'.
const demo = (x: number, y: number):number => {
// return 'abc' // 类型声明.ts:15:5 - error TS2322: Type 'string' is not assignable to type 'number'.
return x + y
}
console.log(demo(100, 200))
// console.log(demo(100, '200')) // 报错
// console.log(demo(100)) // An argument for 'y' was not provide
类型推断
那这个时候就会有人要问,主播主播,我这个js的优势“弱类型”就没了呀,每次定义都需要自己定义一个类型,好麻烦呀
不用担心,TS会出手!
let d = -99 // ts 自动推断类型是数字
当然,你推断后的变量,它自动就定义了类型,如下:
let d = -99
d = false // number不能赋值布尔
类型汇总
JS中的数据类型:
- string
- number
- boolean
- null
- undefined
- bigint
- symbol
- object(包含
Array
、Function
、Date
、Error
等)
TS中的数据类型:
- 上述所有JS的数据类型
- any
- unknown
- never
- void
- tuple
- enum
TS自定义类型的方式:
- type
- interface
tips:
在 JavaScript 中的这些内置构造函数:Number
、String
、Boolean
,用于创建对应的包装对象,在日常开发时很少使用,在TypeScript中也是同理,所以在 TypeScript 中进行类型声明时,通常都是用小写的number
、string
、boolean
例如下面代码:
let str1: string
str1 = 'hello'
// str1 = new String('hello') // 报错 error TS2322: Type 'String' is not assignable to type 'string'.'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible.
let str2: String
str2 = 'hello'
str2 = new String('hello')
console.log(typeof str1)
console.log(typeof str2)
原始类型VS包装对象
原始类型:如number
、string
、boolean
,在 JavaScript 中是简单数据类型,它们在内存中占用空间少,处理速度快。
包装对象:如Number
对象、String
对象、Boolean
对象,是复杂类型,在内存中占用更多空间,在日常开发时很少由开发人员自己创建包装对象。
自动装箱:JavaScript 在必要时会自动将原始类型包装成对象,以便调用方法或访问属性
// 原始类型字符串
let str = 'hello'
// 当访问str.length时, JavaScript引擎做了以下工作:
let size = (() => {
// 1. 自动装箱
let tempStringObject = new String(str)
// 2. 访问String对象的length属性
let lengthValue = tempStringObject.length
// 3. 销毁临时对象, 返回长度值
return lengthValue
})()
常用类型与语法
any
any
的含义:任意类型,一旦变量类型限制为any
,那么就意味着放弃了对该变量的类型检查。
// 明确的表示a的类型是 any —— 【显示的any】
let a: any
// 以下对a的赋值, 均无警告
a = 100
a = '你好'
a = false
// 没有明确的表示b的类型是any, 但TS主动推断出来b是any —— 隐式的any
let b
// 以下对b的赋值, 均无警告
b = 100
b = '你好'
b = false
/* 注意点: any类型, 可以赋值给任意类型的变量 */let c: any
c = 9
let x: string
x = c // 无警告
unknown
unknown
的含义是未知类型,适用于:“起初不确定具体数据类型,要后期才能确定”
unknown
可以理解为一个类型安全的any
// 设置a的类型为unknown
let a: unknown
// 以下对a的赋值, 军符合规范
a = 100
a = false
a = '你好'
// 设置x的数据类型为string
let x: string
x = a // 警告
unknown
会强制开发者在使用之前进行类型检查,从而提供够强的类型安全性
// 设置a的类型为unknown
let u1: unknown
let un;
u1 = 'hello'
// 第一种方式: 加类型判断
if (typeof u1 === 'string') {
un = u1
console.log(un)
}
// 第二种方式:加断言
un = u1 as string
console.log(un)
// 第三种方式:加断言
un = <string>u1
console.log(un)
- 读取
any
类型数据的任何属性都不会报错,而unknown
正好与之相反
let str1: string
str1 = 'hello'
str1.toUpperCase() // 无警告
let str2: any
str2 = 'hello'
str2.toUpperCase() // 无警告
let str3: unknown
str3 = 'hello'
str3.toUpperCase() // 警告
(str3 as string).toUpperCase() // 断言指定为string则无警告
never
never
的含义是:任何值都不是,即“不能有任何值”,例如undefined
、null
、' '
、`0 都不行
- 几乎不用
never
直接限制变量,因为没有意义,例如:
let Never1: never
// 以下赋值均会警告
Never1 = 1
Never1 = true
Never1 = undefined
Never1 = null
never
一般是TyperScript
主动推断出来的,例如:
// 指定类型为string
let Never2: string
// 设置一个值
Never2 = 'hello'
if (typeof Never2 === 'string') {
console.log(Never2.toUpperCase())
} else {
console.log(Never2) // TS将会推断此次的Never2为 never,因为没有一个值符合逻辑
}
never
也可以限制函数的返回值
const throwError= (str: string): never => {
throw new Error('程序异常退出: ' + str )
// return // 警告
}
void
void
的含义是空,即函数不返回任何值,调用者也不应依赖其返回值进行任何操作
简单记: undefined 是 void 可以接受的⼀种“空”。
void
通常用于函数返回值声明
const logMessage = (msg: string): void => {
console.log(msg)
// return '123' // 警告
}
logMessage('你好')
- 以下写法均符合规范
const logMessage0 = (msg: string): void => {
console.log(msg)
}
const logMessage1 = (msg: string): void => {
console.log(msg)
return
}
const logMessage2 = (msg: string): void => {
console.log(msg)
return undefined
}
logMessage0('test')
logMessage1('test1')
logMessage2('test2')
- 返回值类型为 void 的函数,调⽤者不应依赖其返回值进⾏任何操作!对⽐下两段代码,体现
undefined
和void
的区别
const logMsg = (msg: string): void => {
console.log(msg)
}
let result = logMsg('你好')
if(result) console.log('logMessage有返回值') // 报错
const logMsg1 = (msg: string): undefined => {
console.log(msg)
}
let result = logMsg1('你好')
if(result) console.log('logMessage有返回值') // 不报错
object和Object
object
object
(⼩写)的含义是:所有⾮原始类型,可存储:对象、函数、数组等,由于限制
的范围⽐较宽泛,在实际开发中使⽤的相对较少。
Object
- 官⽅描述:所有可以调⽤ Object ⽅法的类型。
- 简单记忆:除了 undefined 和 null 的任何值。
- 由于限制的范围实在太⼤了!所以实际开发中使⽤频率极低。
声明对象类型
- 在实际开发中,限制一般对象,通常使用以下形式
// 限制person1对象必须有name属性, age为可选属性
let person1: { name: string, age?: number }
// 含义同上, 也能用分号做分隔
let person2: { name: string; age?: number }
// 含义同上, 也能用换行做分隔
let person3: {
name: string
age?: number
}
// 以下赋值均可
person1 = { name: 'lisi', age: 18 }
person2 = { name: 'zhangsan' }
// 如下赋值不合法
person3 = { name: 'wangwu', gender: '男'}
- 索引签名:允许定义对象具有任意数量的属性,这些属性的键和类型是可变的,常用于:描述类型不确定的属性,(具有动态属性的对象)。
// 限制person对象必须有name属性,可选age属性但值必须是数字,同时可以有任意数量、任意类型的其他属性
let person: { name: string; age: number; gender: string }
// 赋值合法
person = {
name: '张三',
age: 18,
gender: '男'
}
声明函数类型
let count: (a: number, b: number) => number
count = (x, y) => {
return x + y
}
console.log(count(1, 2))
tips:
TypeScript中的=>
在函数声明时表示函数类型,描述其参数类型和返回类型。
JavaScript中的=>
是一种定义函数的语法,是具体函数实现。
声明数组类型
let arr1: string[]
let arr2: Array<string>
arr1 = ['a', 'b', 'c']
arr2 = ['hello', 'world']
console.log(arr1, arr2)
tuple
元组 (Tuple) 是⼀种特殊的数组类型,可以存储固定数量的元素,并且每个元素的类型是已知的且可以不同。元组⽤于精确描述⼀组值的类型,
?
表示可选元素。
// 第一个元素必须是 string 类型
let arr1: [string, number]
// 第一个元素必须是 number 类型,第二个元素是可选的,如果存在,必须是 boolean 类型
// @ts-ignore
let arr2: [number, boolean?]
// 第一个元素必须是 number 类型,后面的元素可以是任意数量的 string 类型
let arr3: [number, ...string[]]
// 可以赋值
arr1 = ['hello', 123]
arr2 = [100, false]
arr2 = [200]
arr3 = [100, 'hello', 'world']
arr3 = [100]
// 不可以赋值
arr1 = ['hello', 123, false]
enum
枚举(
enum
)可以定义一组命名常量,它能增强代码的可读性,也可以让代码更好维护。
如下代码的功能是:根据调⽤ walk 时传⼊的不同参数,执⾏不同的逻辑,存在的问题是调⽤ walk 时传参时没有任何提示,编码者很容易写错字符串内容;并且⽤于判断逻辑的 up 、 down 、 left 、 right 是连续且相关的⼀组值,那此时就特别适合使⽤ 枚举( enum )
。
const walk = (str: string):void => {
if (str === 'up') console.log('向【上】走')
else if (str === 'down') console.log('向【下】走')
else if (str === 'left') console.log('向【左】走')
else if (str === 'right') console.log('向【右】走')
else console.log('未知方向')
}
walk('up')
walk('down')
walk('left')
walk('right')
数字枚举
数字枚举是最常见的枚举类型,其成员的值会自动递增,且数字枚举还具备反向映射的特点,以下代码展示:
// 定义一个描述【上下左右】方向的枚举Direction
enum Direction {
Up,
Down,
Left,
Right
}
console.log(Direction)
/*
Direction输出内容:
{
'0': 'Up', '1': 'Down', '2': 'Left', '3': 'Right', Up: 0, Down: 1, Left: 2, Right: 3}
*/
// 反向映射
console.log(Direction.Up)
console.log(Direction[0])
// Direction.Up = 'shang' // 报错,枚举中的属性是只读的
通过上方代码可知:【可以通过值来获取枚举成员名称】
也可以指定枚举成员的初始值,其后的成员值会自动递增
enum Direction {
Up = 6,
Down,
Left,
Right
}
console.log(Direction.Up) // 6
console.log(Direction.Down) // 7
回到最开始的程序,我们使用枚举改造一下:
enum Direction {
Up,
Down,
Left,
Right
}
const walk = (n: Direction): void => {
if (n === Direction.Up) console.log("向【上】走")
else if (n === Direction.Down) console.log("向【下】走")
else if (n === Direction.Left) console.log("向【左】走")
else if (n === Direction.Right) console.log("向【右】走")
else console.log("未知方向")
}
walk(Direction.Up)
walk(Direction.Down)
字符串枚举
枚举成员的值是字符串
enum Direction {
Up = "up",
Down = "down",
Left = "left",
Right = "right"
}
let dir: Direction = Direction.Up
console.log(dir) // up
常量枚举
使用普通枚举的TS代码:
enum Directions {
Up,
Down,
Left,
Right
}
let x = Directions.Up;
编译生成后的JS代码量较大:
use strict";
var Directions;
(function (Directions) {
Directions[Directions["Up"] = 0] = "Up";
Directions[Directions["Down"] = 1] = "Down";
Directions[Directions["Left"] = 2] = "Left";
Directions[Directions["Right"] = 3] = "Right";
})(Directions || (Directions = {}));
let x = Directions.Up;
使用常量枚举TS代码如下:
const enum Directions {
Up,
Down,
Left,
Right
}
let x = Directions.Up;
编译后生成的JS代码量较小:
"use strict";
let x = 0 /* Directions.Up */;
TS自定义类型
type
type
可以为任意类型创建别名,让代码更简洁、可读性更强,同时能更方便地进行类型复用和拓展。
基本用法
类型别名使用 type
关键字定义,type
后跟类型名称,如下:
type num = number
let price: num
price = 100
联合类型
联合类型是一种高级类型,它表示一个值可以是几种不同类型之一。
type Status = number | string
type Gender = '男' | '女'
const printStatus = (status: Status): void => {
console.log(status)
}
const logGender = (str: Gender): void => {
console.log(str)
}
printStatus(404)
printStatus('200')
printStatus('501')
// printStatus(true) // 报错
logGender('男')
logGender('女')
// logGender('a') // 报错
交叉类型
交叉类型(Intersection Types)允许将多个类型合并为一个类型。合并后的类型将拥有所有被合并类型的成员。交叉类型通常用于对象类型。
// 面积
type Area = {
height: number // 高
width: number
}
// 地址
type Address = {
num: number // 楼号
cell: number // 单元号
room: string // 房间号
}
// 定义类型House,且House是Area和Address组成的交叉类型
type House = Area & Address
const house: House = {
height: 180,
width: 75,
num: 6,
cell: 3,
room: '701'
}
console.log(house)
一个特殊情况
代码段1(正常)
function demo():void{
// 返回undefined合法
return undefined
// 以下返回均不合法
return 100
return false
return null
return []
}
demo()
代码段2(特殊)
type LogFunc = () => void
const f1: LogFunc = () => {
return 100; // 允许返回⾮空值
};
const f2: LogFunc = () => 200; // 允许返回⾮空值
const f3: LogFunc = function () {
return 300; // 允许返回⾮空值
};
const src = [1, 2, 3];
const dst = [0];
src.forEach((el) => dst.push(el));
- 感谢你赐予我前进的力量