🚌 TypeScript 变量的声明与数据类型
# 变量的声明
在 TypeScript 中定义变量需要指定「标识符」的类型。
所以完整的声明格式如下:
var/let/const 标识符: 数据类型 = 赋值;
声明了类型后 TypeScript 就会进行类型检测,声明的类型可以称之为类型注解;
🌰 声明一个字符串类型的 message
变量,完整的写法如下:
let message: string = 'hello typescript';
注意:小写
string
(与String
有区别)是 TypeScript 中定义的字符串类型,String 是 ECMAScript 中定义的一个。这时给
message
赋其他类型的值,就会报错。
# 声明变量的关键字
在 TypeScript 定义变量(标识符)和 ES6 之后一致,可以使用 var
、 let
、 const
来定义。
var myname: string = 'why';
let myage: number = 20;
const myheight : number = 1.88;
2
3
注意:在 tslint 中并不推荐使用
var
来声明变量。主要原因和 ES6 升级后let
和var
的区别是一样的,var
是没有块级作用域的,可能会引起许多问题。
# 变量的类型推断
在开发中,有时候为了方便起见并不会在声明每一个变量时都写上对应的数据类型,更希望可以通过 TypeScript 本身的特性帮助我们推断出对应的变量类型。
🌰 例子:
let message = 'hello typescript'
// 此时下文再赋值数字类型就会报错
message = 123
2
3
TypeScript 在一个变量第一次赋值时,会根据后面的赋值内容的类型,来推断出变量的类型。
# 数据类型
# JavaScript 类型
# number 数字类型
TypeScript 和 JavaScript 一样,不区分整数类型(int)和浮点型 (double),统一为 number 类型。
- 进制数字类型:TypeScript 与 ES6 同样支持二进制、八进制、十 六进制的表示:。
# boolean 布尔类型
只有两个取值 true
和 false
。
# string 类型
字符串类型,可以使用单引号或者双引号表示;
与 ES6 同样支持模版字符串拼接变量和字符串。
Array
数组类型:两种定义的方式:const names: string[] = ["abc", "cba", "cba"] const names2: Array<string> = ["abc", "cba", "nba"] names.push("why") names2.push("why")
1
2
3
4
5数组中的数据类型只能为声明时的数据类型。
# object
对象类型
描述一个对象:
const myInfo: object = {
name: "simon",
age: 18,
height: 1.88
}
2
3
4
5
但是不能直接获取数据也不能设置数据。
# symbol
类型
通过 symbol
来定义相同的名称的变量,因为 Symbol
函数返回的是不同的值。
const s1: symbol = Symbol("title")
const s2: symbol = Symbol("title")
const person = {
[s1]:"程序员",
[s2]:"老师"
}
2
3
4
5
6
# null/undefined
类型
在 TypeScript 中,它们各自的类型也是 undefined
和 null
,也就意味着它们既是实际的值,也是自己的类型。
let n: null = null
let u: undefined = undefined
2
# TypeScript 类型
# any
类型
当确实无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们可以使用 any
类型(类似于 Dart 语言中的 dynamic 类型)如果对于某些情况的处理过于繁琐不希望添加规定的类型注解,或者在引入一些第三方库时,缺失了类型注解,这个时候可以使用 any
(包括在 Vue 源码中,也会使用到 any
来进行某些类型的适配)。
any
类型有点像一种讨巧的 TypeScript 手段:- 可以对
any
类型的变量进行任何的操作,包括获取不存在的属性、方法; - 可以给一个
any
类型的变量赋值任何的值,比如数字、字符串的值;
let a: any = "why";
a = 123;
a = true;
const aArray: any[] = ["why", 18, 1.88];
2
3
4
# unknown
类型
是 TypeScript 中比较特殊的一种类型,它用于描述类型不确定的变量。
function foo(): string {
return "foo!"
}
function bar(): number {
return 123;
}
const flag = true
let result: unknown
if(flag) {
result = foo()
} else {
result = bar()
}
if(typeof result === "string"){
console.log(result.length)
}
export {}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# void
类型
通常用来指定一个函数是没有返回值的,那么它的返回值就是 void
类型。可以将 null
和 undefined
赋值给 void
类型,也就是函数可以返回 null
或者 undefined
。
function sum(num1: number, num2: number): void{
console.log(num1 + num2)
} // 没有写任何类型,默认的返回值类型就是void
2
3
# never
类型
表示永远不会发生值的类型,比如一个函数。
如果一个函数中是一个死循环或者抛出一个异常,该函数不会返回任何类型的值,所以写
void
类型或者其他类型都不合适,就可以使用never
类型。function loopFun(): never { while(true) { console.log("123") } } function LoopErr(): never { throw new Error() }
1
2
3
4
5
6
7
8
9function handleMessage(message: number|string) { switch (typeof message) { case 'string': console.log('foo') break case 'number': console.log('bar') break default: const check: never = message } }
1
2
3
4
5
6
7
8
9
10
11
12
# tuple
类型
是元组类型,很多语言中也有这种数据类型,比如 Python、Swift 等。
const tInfo: [string, number,number] = ["why", 18, 1.88];
const item1 = tInfo[0] // why,并且知道类型是string类型
const item2 = tInfo[1];// 18,并且知道类型是number类型
2
3
tuple
类型与数组类型的区别:数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中。(不同类型的数据可以存放对象或元组)元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型tuple
的应用场景:通常作为返回的值,使用的时候很方便:
function useState<T>(state: T): [T, (newState: T) => void]{
let currentState = state
const changeState = (new State: T)=>{
currentState = newState
}
return [currentState, changeState]
}
const [counter, setCounter] = useState(10)
2
3
4
5
6
7
8
9
# 函数类型
# 函数的参数类型
函数是 JavaScript 非常重要的组成部分,TypeScript 允许指定函数的参数和返回值的类型。
在声明函数时,可以在每个参数后添加类型注解,以声明函数接受的参数类型:
function greet (name: string) {
console. log("Hello" + name.toUpperCase())
}
greet(123) // 报错,参数类型有误
greet("abc", "cba") // 报错,参数数量有误
2
3
4
5
6
# 函数的返回值类型
添加返回值类型的注解,放在函数列表的后面:
function sum (num1: number, num2: number): number {
return num1 + num2
}
2
3
和变量的类型注解一样,通常情况下不需要返回类型注解,因为 TypeScript 会根据 return
返回值推断函数的返回类型。(某些第三方库处于方便理解,会明确指定返回类型)
# 匿名函数的参数
匿名函数与函数声明会有一些不同:
- 当一个函数出现在 TypeScript 可以确定该函数会被如何调用的地方时;
- 该函数的参数会自动指定类型;
🌰 例子:
const names = ['abc', 'cba', 'nba']
names.forEach(item => {
console.log(item.toUpperCase())
})
2
3
4
并没有指定
item
的类型,但是item
是一个string
类型:
- 并没有指定
item
的类型,但是item
是一个string
类型;- 过程称之为上下文类型(contextual typing),因为函数执行的上下文可以帮助确定参数和返回值的类型。
# 对象类型
当希望限定一个函数接受的参数是一个对象:
可以使用对象类型:
function printCoordinate(point: {x: number, y: number}) { console.log("x坐标:", point.x) console.log("y坐标:", point.y) } printCoordinate({x: 10,y: 30})
1
2
3
4
5
6这里使用了一个对象来作为类型:
- 在对象可以添加属性,并且告知 TypeScript 该属性需要是什么类型;
- 属性之间可以使用,或者
;
来分割,最后一个分隔符是可选的; - 每个属性的类型部分也是可选的,如果不指定,那么就是
any
类型;
# 可选属性类型
对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个 ?
。
function printCoordinate(point: {x: number, y: number, z?: number}) {
console. 1og("x坐标:",point.x)
console. log("y坐标:",point.y)
if (point.z) {
console.log("z坐标:",point.z)
}
}
printCoordinate({x: 10, y: 30})
printCoordinate({x: 20,y: 30, z: 40})
2
3
4
5
6
7
8
9
10
# 联合类型
TypeScript 的类型系统允许使用多种运算符,从现有类型中构建新类型。使用第一种组合类型的方法:联合类型(Union Type)
- 联合类型是由两个或者多个其他类型组成的类型;
- 表示可以是这些类型中的任何一个值;
- 联合类型中的每一个类型被称之为联合成员(union's members);
function printId(id: number | string) {
console.1og("你的id是:",id)
}
printId(10)
printId("abc")
2
3
4
5
6
# 函数中使用联合类型
传入给一个联合类型的值是非常简单的:
- 只要保证是联合类型中的某一个类型的值即可
- 但是拿到这个值之后,应该如何使用它呢?因为它可能是任何一种类型。
- 比如我们拿到的值可能是
string
或者number
,我们就不能对其调用string
上的一些方法。
解决方法:
- 需要使用缩小(narrow)联合;
- TypeScript 可以根据我们缩小的代码结构,推断出更加具体的类型;
function printId(id: number | string) {
if(typeof id === 'string'){
//确定id是string类型
console.log("你的id是:", id.toUpperCase())
} else {
//确定id是number类型
console.log("你的id是", id)
}
}
2
3
4
5
6
7
8
9
# 可选类型补充
可选类型可以看做是 类型 和 undefined
的联合类型:
function print(message?: string) {
console.log(message)
}
print()
print('xx')
print(undefined)
print(null) // 报错,不是两者其中之一的类型
2
3
4
5
6
7
8
# 类型别名
此前通过在类型注解中编写 对象类型 和 联合类型,当想要要多次在其他地方使用时,就要编写多次。此时可以给对象类型起一个别名,使用 type
声明。
type ID = number | string
function printId(id: ID) {
console.log("您的id:",id)
}
2
3
4
5
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: 20, y: 20})
sumPoint({x: 20, y: 20})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 类型断言
有时候 TypeScript 无法获取具体的类型信息,这时需要使用类型断言(Type Assertions)。
例子:比如通过 document.getElementById
,TypeScript 只知道该函数会返回 HTMLElement
,但并不知道它具体的类型。可以使用 as
进行类型断言:
const myEl = document.getElementById("my-img") as HTMLImageELement
myEl.src="图片地址"
2
TypeScript 只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换:
const name = 'string' as number; // 不能转换,因为已经确定为字符串类型
const name = ('string' as unknown) as number;
2
# 非空类型断言
当编写一下代码时,执行 ts 的编译阶段会报错:
function printMessage(message?: string) {
console.log(message.toUpperCase()) // 此时传入的message有可能是undefined,所以不能执行方法
}
printMessage('hello')
2
3
4
5
但是,我们确定传入的参数是有值的,此时可以使用非空类型断言。
function printMessage(message?: string) {
console.log(message!.toUpperCase())
}
2
3
非空断言使用的是 !
,表示可以确定某个标识符是有值的,跳过 ts 在编译阶段对它的检测;
# 可选链的使用
可选链并不是 TypeScript 独有的特性,它是 ES11(ES2020)中增加的特性。
- 可选链使用可选链操作符
?
; - 作用:当对象的属性不存在时,会短路直接返回
undefined
;如果存在,那么才会继续执行。
type Person = {
name: string
friend?: {
name: string
age?: number,
girlFriend? :{
name: string
}
}
}
const info: Person = {
name: 'why',
friend: {
name: 'kobe',
girlFriend: {
name: 'lily'
}
}
}
console.log(info.friend?.name)
console.log(info.friend?.age)
console.log(info.friend?.girlFriend?.name)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ??
和 !!
的使用
!!
操作符:
- 将一个其他类型转换成
boolean
类型; - 类似于
Boolean(变量)
的方式;
const message = ''
let flag = Boolean(message)
let flag2 = !!messgae
console.log(flag1, flag2) // false, false
2
3
4
??
操作符:
- 是 ES11 增加的新特性;
- 空值合并操作符(
??
)是一个逻辑操作符,当操作符的左侧是null
或者undefined
时,返回其右侧操作数, 否则返回左侧操作数;
const message = '321'
const result = message ?? '123'
console.log(result) // message不为空所以返回message本身内容
2
3
# 字面量类型
除了前面的类型以外,也可以使用字面量类型(literal types)定义。
let message: 'hello world' = 'hello world'
message = 'hello typescript' // 只能声明为 'hello world'
2
上面的例子看上去没有太大的意义,但是可以讲多个字面量类型联合在一起:
type Alignment = 'left' | 'right' | 'center'
function changeAlign(align: Alignment) {
console.log('修改方向', align)
}
2
3
4
# 字面量推理
const info = {
url: 'https://github.com/',
method: 'GET'
}
function request(url: string, method: 'GET'|'POST') {
console.log(url, method)
}
request(info.url, info.method) // 第二个参数报错
2
3
4
5
6
7
8
9
10
因为对象在进行字面量推理的时候, info
其实是一个 {url: string, method: string}
,所以不能把一个 string
赋值给一个字面量类型。
# 类型缩小
(Type Narrowing)
- 通过类似于
typeof padding === ‘number’
的判断语句,改变 TypeScript 的执行路径。 - 在给定的执行路径,缩小比声明时更小的类型,这个过程称之为类型缩小。
typeof padding === ‘number’
可以称为 类型保护(type guards).
常见类型保护有:
typeof
- 平等缩小(
===
、!==
) intanceof
in
- …
# typeof
在 TypeScript 中,检查返回的值 typeof
是一种类型保护:因为 TypeScript 对如何 typeof
操作不同的值进行编码。
type ID = number | string
function printId(id: ID) {
if(typeof id === 'string'){
consosle.log(id.toUpperCase())
} else {
console.log(id)
}
}
2
3
4
5
6
7
8
# 平等缩小
可以使用 switch
或者相等的一些运算符来表达相等性( ===
、 !==
、 !=
)
type Direction = 'left'|'right'|'center'
function turnDirection(direction: Direction){
switch(direction){
case 'left':
console.log('left')
break;
case 'right':
console.log('right')
break;
case 'center':
console.log('center')
break;
default:
console.log('default')
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# instanceof
JavaScript 中 instanceof
运算符来检查一个值是否是另一个值的「实例」。
function printValue(date: Date|string){
if(date instanceof Date){
console.log(date.toLocaleString())
} else {
console.log(date)
}
}
2
3
4
5
6
7
# in
JavaScript 运算符 in
用于确定对象是否具有带名称的属性:
- 如果指定的属性在指定的对象或其原型链中,则
in
运算符返回true
。
type Fish = {swim: () => void}
type Dog = {run: () => void}
function move(animal: Fish | Dog) {
if('swim' in animal) {
animal.swim()
} else {
animal.run()
}
}
2
3
4
5
6
7
8
9
10
# 枚举类型
枚举类型是为数不多的 TypeScript 特性有的特性之一:
枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型;
枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型。
枚举类型默认是有值的,默认值如下;
enum Direction { LEFT = 0, RIGHT = 1, TOP = 2, BOTTOM = 3 }
1
2
3
4
5
6
7当然也可以给枚举其他值: 这个时候会从 100 进行递增;
enum Direction { LEFT = 100, RIGHT, TOP, BOTTOM }
1
2
3
4
5
6也可以给他们赋值其他的类型:
enum Direction { LEFT, RIGHT, TOP = 'TOP', BOTTOM = 'BOTTOM' }
1
2
3
4
5
6
enum Direction {
LEFT,
RIGHT,
TOP,
BOTTOM
}
function turnDirection (direction: Direction) {
switch (direction) {
case Direction.LEFT:
console.log("转向左边~")
break;
case Direction.RIGHT:
console.log("转向右边~")
break;
case Direction.TOP:
console.log("转向上边~")
break;
case Direction. BOTTOM:
console.log("转向下边~")
break;
default:
const myDirection: never = direction;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25