搭建 TypeScript 的环境
TypeScript 代码是不能直接在浏览器或者 node 环境下运行的,要先将其编译成 JavaScript 代码才可以。我们可以通过 TypeScript Compiler (tsc) 来实现TypeScript 代码的编译。
编译完成后会生成一个对应的 js 文件。
tsc 只是将 TypeScript 代码编译成了 JavaScript 代码,并没有去执行这段代码。如果想要在编译之后直接执行代码的话,可以通过 webpack 来配置,或者是通过 ts-node 库来实现。
1. 使用 ts-node 库来执行 TypeScript 代码(node 环境下)
安装 ts-node 库及其依赖
npm install ts-node -g
npm install tslib @types/node -g
使用 ts-node 库来执行 TypeScript 代码
ts-node 库并不会生成对应的 js 文件,它只是在编译完成后直接去执行编译好的代码。
2. 使用 webpack 配置 TypeScript 的环境
TypeScript 语法
TypeScript 中的变量声明
类型注解
在 TypeScript 语法中,声明变量时需要声明类型,声明了类型后 TypeScript 就会进行类型检测。
具体的类型检测包括以下几个方面:
-
确定了类型的变量初始化时赋值其他类型的值编辑器会报错;
-
确定了类型的变量在修改值时赋值其他类型的值编辑器会报错;
-
确定了类型的变量只能赋值给相同数据类型的变量。
如下例:
let username: string = 'kobe'
当我们给 username 赋值其他类型的值,编辑器就会提示类型错误:
类型推导
在开发中,有时候为了方便起见,我们并不会在声明每一个变量时都写上对应的数据类型,这时我们可以通过 TypeScript 本身的特性帮助我们推断出对应的变量类型。
同样的例子,我们在声明时没有声明类型,但是当我们赋值其他类型的值时,编辑器也会提示类型错误:
let username = 'kobe'
这是因为在一个变量第一次赋值时,会根据后面的赋值内容的类型,来推断出变量的类型。
也就是说,我们在实际开发过程中,为了方便可以像在 JavaScript 代码一样来声明变量,不同的是在 TypeScript 中给变量赋值之后,它的数据类型就确定下来了。
TypeScript 中的数据类型
变量的数据类型
关键字类型
JavaScript 中已有的数据类型
string类型
const msg: string = 'hello world'
number类型
const num: number = 123
boolean类型
const flag: boolean = true
Array类型
因为在 JavaScript 中数组的元素可以是任何类型的数据,所以为了规范,在 TypeScript 中数组的元素必须都是同一类型。 如果希望定义不同类型的数据,可以使用 元组,但是元组中每个元素的数据类型也必须定义好。
在 TypeScript 中,定义数组的方法有 2 种
const arr: Array<string> = []
const arr: string[] = []
第一种方式不常用,因为这种写法在 jsx 中会引起歧义。
我们在 TypeScript 中定义数组时,所有元素是同一类型。
我们也可以定义不同类型数据的数组,但是这时,数组中的每个元素的类型都是无法确定的。
const arr: (string | number)[] = ['kobe', 18, 1.88]
const name = arr[0] // 这时 name 的数据类型是 string | number
所以我们一般不会这样做,如果是不同类型的数据组合,我们一般用 元组(tuple)。
Object类型
对象类型可以直接用关键字声明
const info: object = {
name: 'abc',
age: 123,
}
但是这样的话就无法获取到对象里的属性。因为相当于下面代码:
const info: {} = {
name: 'abc',
age: 123,
}
所以我们一般是这样声明:在声明对象时要把对象所拥有的属性及值的类型定义下来,起到约束作用。
const info: { name: string; age: number } = {
name: 'abc',
age: 123,
}
Symbol类型
null类型
undefined类型
联合类型
联合类型表示该变量有多个类型可能,只要满足其中之一即可。
let msg: string | number = 'hello world'
msg = 123
交叉类型
交叉类型表示该变量的类型必须同时满足多个可能,对于简单数据类型显然是不可能的,所以交叉类型一般用于定义对象类型的数据。
let msg: string & number // 此时 msg 是 never 类型
msg = 123 // 会报错
TypeScript 中新增的数据类型
any 类型
当我们定义了一个变量,会涉及到两个方面,一个是它的 数据类型,另一个是它的 值。当一个变量的数据类型确定了,它所能够赋值的范围也就确定了。例如,string 类型的变量它可赋的值就是所有带有引号的字符,number 类型就是所有进制的数字,boolean 类型就只有 true 跟 false,null 类型跟 undefined 类型的值就只有它们本身等等。
而当一个变量的数据类型是 any 类型时,它可以被赋值所有其他类型的值,比如字符串或者是数字。
let username: any = 'kobe'
username = 123 // 这时编辑器不会提示错误
但是不管被赋予的值是什么类型,这个变量本身还是 any 类型。
any 类型的变量是可以赋值给其他类型的。
let username: any = 'kobe'
let msg: string = username // 编辑器不会提示错误
unknown 类型
unknown 是 TypeScript 中比较特殊的一种类型,它用于描述类型不确定的变量。它也可以被赋值所有其他类型的值。
let flag: unknown = true
flag = 123 // 编辑器不会提示错误
unknown 类型跟 any 类型的区别在于,unknown 类型的变量只能赋值给 unknown 类型和 any 类型的变量,不能赋值给其他类型的变量,这样就避免了一些问题的出现。
所以我们在实际开发中要尽量少使用 any 类型,如果无法确定变量的类型,我们应该使用unknown 类型来声明。
never 类型
tuple 类型
元组(tuple)类型,可以看成多种元素的组合。
const arr: [string, string, boolean] = ['kobe', '篮球', true]
元组的优点在于,元组中每个元素的数据类型都是可以确定的。(见数组)
// 数组
const arr: (string | boolean)[] = ['kobe', '篮球', true]
const name = arr[0] // 这时 name 的数据类型是 string | boolean
// 元组
const arr: [string, string, boolean] = ['kobe', '篮球', true]
const name = arr[0] // 这时 name 的数据类型就是 string
字面量类型(自定义的类型)
我们在定义变量的类型时除了使用常见的数据类型,也可以自定义类型。自定义类型变量的值是唯一的,就是类型本身。
let msg: 'hello world' = 'hello world'
字面量的联合类型
变量的类型也可以是多个字面量类型的联合类型,这个时候它的值可以是几个字面量的其中一个。
let align: 'left' | 'center' | 'right' = 'left'
类的类型
变量的类型也可以是一个类,这时变量的值是一个对象。
class Person {
name: string = ''
age: number = 0
}
const p: Person = {
name: 'kobe',
age: 18,
}
函数的类型
函数参数的类型
在 TypeScript 我们可以指定函数的参数和返回值的类型。
函数的参数相当于直接定义的变量。
声明函数时,可以在每个参数后添加类型注解,以声明函数接受的参数类型。
function sum(num1: number, num2: number) {}
函数参数的联合类型
函数的参数也可以是联合类型的
function showId(id: number | string) {}
showId('abc')
showId(1)
可选类型
如果我们希望某个参数是可选的,可以在属性的后面添加一个 ?
function showNum(num1: number, num2?: number) {}
showNum(10, 20)
showNum(10)
可选类型相当于是该类型跟 undefined 类型的联合类型
function showNum(num1?: number) {}
// 相当于
function showNum(num1: number | undefined) {}
如果有多个参数,可选参数必须放在必选参数后面。
参数的默认值 (ES6)
函数的参数可以设置默认值
function showNum(num1: number, num2: number = 20) {}
showNum(10) // 10, 20
在写函数的参数时,可以按照 必传参数 --> 默认参数 --> 可选参数 的顺序来写。
function showNum(num1: number, num2: number = 20, num3?: number) {
console.log(num1, num2, num3)
}
showNum(10, undefined, 30)
上下文类型(匿名函数)
如果函数执行的上下文可以推断出参数的类型,那么我们可以不给参数添加类型注解。
const arr = ['kobe', 'james']
arr.forEach((item) => {
console.log(item)
})
这里 item 的类型是可以通过 arr 推导出来的,所以这里我们可以不给它添加类型注解。
对象类型
如果我们希望限定一个函数接受的参数是一个对象,可以通过以下方式来限定
function showInfo(info: { name: string; age: number; height: number }) {
console.log(info.name)
console.log(info.age)
console.log(info.height)
}
showInfo({ name: 'kobe', age: 18, height: 1.88 })
对象类型也可以指定哪些属性是可选的,在属性的后面添加一个 ? 即可
function showInfo(info: { name: string; age: number; height?: number }) {
console.log(info.name)
console.log(info.age)
console.log(info.height)
}
showInfo({ name: 'kobe', age: 18})
函数返回值的类型(一般不用特地添加)
我们也可以添加函数返回值的类型注解,这个注解出现在函数列表的后面。
function sum(num1: number, num2: number): number {}
一般情况下我们不需要给函数的返回值添加类型注解,因为 TypeScript 会根据函数体的返回值来推断返回值的数据类型。也就是
function sum(num1: number, num2: number): number {
return num1 + num2
}
跟
function sum(num1: number, num2: number) {
return num1 + num2
}
是相同的。
函数本身的类型
在 TypeScript 中函数也是有类型的,函数的类型格式是 () => void
声明函数类型的方式有两种。
没有声明类型的函数:
function fn(num1: number, num2: number) {
return num1 + num2
}
第一种方式,使用函数表达式来声明:
const fn: (num1: number, num2: number) => number = (num1: number, num2: number) => {
return num1 + num2
}
// 或者是
const FnType = (num1: number, num2: number) => number
const fn: FnType = (num1: number, num2: number) => {
return num1 + num2
}
第二种方式,作为函数的参数声明:
function foo(fn: (num1: number, num2: number) => number) {}
// 或者是
const FnType = (num1: number, num2: number) => number
function foo(fn: FnType) {}
注意,函数类型中的返回值类型必须确定,不能省略。如果没有返回值,就是 void
函数重载
类型别名
当我们定义联合类型。或者是给一个对象定义类型时,就可以给它们起一个别名,以便于我们使用。
类型别名使用 type 关键字来定义
type IDType = string | number
let id: IDType
type IDType = string | number
function showId(id: IDType) {}
type infoType = { name: string; age: number; height?: number }
function showInfo(info: infoType) {}
类型缩小
我们在定义了变量的类型后,有可能这个类型是一个比较宽泛的类型,或者是联合类型。而在使用时我们可能会对某些具体类型的进行一些操作,这时可以通过类型缩小来实现。
实现类型缩小的过程我们称之为 类型保护。常用的类型保护有以下几种方式:
- typeof —— 一般是用在关键字类型参数情况下
type IDType = string | number
function printId(id: IDType) {
console.log(id) // 这时 id 的类型是 string | number
if (typeof id === 'string') {
console.log(id.length) // 这时 id 的类型是 string
} else {
console.log(id) // 这时 id 的类型是 number
}
}
printId(123)
- (=== == !== !=) / switch —— 一般是用在字面量类型参数情况下
type Direction = 'left' | 'right'
function printDirection(direction: Direction) {
console.log(direction)
if (direction === 'left') {
console.log('我在左边!')
} else {
console.log('我在右边!')
}
}
printDirection('left')
- instanceof —— 一般是用在参数是类生成的实例的情况下
class Student {
study() {
console.log(1)
}
}
class Teacher {
teaching() {
console.log(2)
}
}
function work(p: Student | Teacher) {
if (p instanceof Student) {
p.study()
} else {
p.teaching()
}
}
const stu = new Student()
work(stu)
- in —— 一般是用在对象类型参数的情况下
type Fish = {
swimming: string
}
type Dog = {
running: string
}
function walk(animal: Fish | Dog) {
if ('swimming' in animal) {
console.log('fish')
} else {
console.log('dog')
}
}
const fish: Fish = {
swimming: 'abc',
}
walk(fish)