TypeScript 语法(一)

265 阅读10分钟

搭建 TypeScript 的环境

TypeScript 代码是不能直接在浏览器或者 node 环境下运行的,要先将其编译成 JavaScript 代码才可以。我们可以通过 TypeScript Compiler (tsc) 来实现TypeScript 代码的编译。

image.png

image.png

image.png

编译完成后会生成一个对应的 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 代码

image.png

image.png

ts-node 库并不会生成对应的 js 文件,它只是在编译完成后直接去执行编译好的代码。

2. 使用 webpack 配置 TypeScript 的环境

TypeScript 语法

TypeScript 中的变量声明

类型注解

TypeScript 语法中,声明变量时需要声明类型,声明了类型后 TypeScript 就会进行类型检测。

具体的类型检测包括以下几个方面:

  • 确定了类型的变量初始化时赋值其他类型的值编辑器会报错;

  • 确定了类型的变量在修改值时赋值其他类型的值编辑器会报错;

  • 确定了类型的变量只能赋值给相同数据类型的变量。

如下例:

let username: string = 'kobe'

当我们给 username 赋值其他类型的值,编辑器就会提示类型错误:

image.png

类型推导

在开发中,有时候为了方便起见,我们并不会在声明每一个变量时都写上对应的数据类型,这时我们可以通过 TypeScript 本身的特性帮助我们推断出对应的变量类型。

同样的例子,我们在声明时没有声明类型,但是当我们赋值其他类型的值时,编辑器也会提示类型错误:

let username = 'kobe'

image.png

这是因为在一个变量第一次赋值时,会根据后面的赋值内容的类型,来推断出变量的类型。

也就是说,我们在实际开发过程中,为了方便可以像在 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 类型就只有 truefalsenull 类型跟 undefined 类型的值就只有它们本身等等。

而当一个变量的数据类型是 any 类型时,它可以被赋值所有其他类型的值,比如字符串或者是数字。

let username: any = 'kobe'

username = 123 // 这时编辑器不会提示错误

但是不管被赋予的值是什么类型,这个变量本身还是 any 类型。

any 类型的变量是可以赋值给其他类型的。

let username: any = 'kobe'

let msg: string = username  // 编辑器不会提示错误

unknown 类型

unknownTypeScript 中比较特殊的一种类型,它用于描述类型不确定的变量。它也可以被赋值所有其他类型的值。

let flag: unknown = true

flag = 123  // 编辑器不会提示错误

unknown 类型跟 any 类型的区别在于,unknown 类型的变量只能赋值给 unknown 类型和 any 类型的变量,不能赋值给其他类型的变量,这样就避免了一些问题的出现。

image.png

所以我们在实际开发中要尽量少使用 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)