快速上手TypeScript

188 阅读7分钟

笔记整理-主要参考:vue3.chengpeiquan.com/

前言

  • TS编译环境

    • 全局安装:npm install typescript -g tsc --version
  • 使用ts-node

    • 安装ts-node:npm install ts-node -g
    • ts需依赖tslib和@type/node两个包:npm install tslib @types/node -g
    • 用ts-node运行TS代码:ts-node math.ts

选择类型系统(TypeScript)原因

  • JavaScript过于灵活,没有类型约束,因类型变化会导致一些bug(程序运行时才会被发现,容易引起生产事故),TypeScript可避免以上情况

类型定义

原始数据类型

  • string字符串、number数值、boolean布尔值、bigint大整数、symbol符号、null不存在、undefined未定义
  • TypeScript与JavaScript原始数据类型唯一区别:TS是小写,JS是大写
  • 小驼峰命名法
//定义原始数据类型
const str:string = 'Hello'
const numTotal:number = 12
const isFlag:boolean = true
//TS推导他们的类型(不会报错)
const str1='Hello'

数组(引用类型)Array

  • 写法:原始数据类型[]Array<原始数据类型>
  • 说明:

    • Array<原始数据类型>是基于TS的泛型Array<T>
    • 是最接近原始数据类型的引用类型
    • 若不写明类型,会根据值推导;没有值会认为是any[]或nerve类型
// 字符串数组
const strs: string[] = ['Hello World', 'Hi World']
// 数值数组
const nums: number[] = [1, 2, 3]
// 布尔值数组
const bools: boolean[] = [true, true, false]

// 这种有初始项目的数组, TS 也会帮推导它们的类型
const strs = ['Hello World', 'Hi World']
const nums = [1, 2, 3]
const bools = [true, true, false]

// 这个时候会认为是 any[] 或者 never[] 类型
const nums = []
// 这个时候再 push 一个 number 数据进去,也不会使其成为 number[]
nums.push(1)

对象(接口-引用类型)Object

  • 定义方式:首字母大写(大驼峰命名)

    • interface UserInfo{},无等号,比较常用
    • type UserInfo={},有等号

  • 定义对象中属性的数据类型

    • 定义对象中属性的数据类型,声明对象时需要与之对应(不要缺少属性)

    • 可选的接口属性

    • 可调用自身接口的属性

    • 接口的继承(extends

      • 继承别的接口所有属性
      • 继承别的接口部分属性(别的接口,不被继承的属性 应该是可选的)
      • 继承时,删除多余的属性:Omit<对象名,'属性1'|'属性2'>
//1. 正常的代码
// 定义用户对象的类型
interface UserItem {
  name: string
  age: number
}
// 在声明变量的时候将其关联到类型上
const petter: UserItem = {
  name: 'Petter',
  age: 20,
}

//2. 错误的代码
// 注意!这是一段会报错的代码
interface UserItem {
  name: string
  age: number
}
const petter: UserItem = {
  name: 'Petter',
}

//3. 正常的代码
interface UserItem {
  name: string
  // 这个属性变成了可选
  age?: number
}
const petter: UserItem = {
  name: 'Petter',
}

//4. 调用自身的接口属性
interface UserItem {
  name: string
  age: number
  enjoyFoods: string[]
  // 这个属性引用了本身的类型
  friendList: UserItem[]
}
const petter: UserItem = {
  name: 'Petter',
  age: 18,
  enjoyFoods: ['rice', 'noodle', 'pizza'],
  friendList: [
    {
      name: 'Marry',
      age: 16,
      enjoyFoods: ['pizza', 'ice cream'],
      friendList: [],
    },
    {
      name: 'Tom',
      age: 20,
      enjoyFoods: ['chicken', 'cake'],
      friendList: [],
    }
  ],
}

//5. 接口的继承
//5.1 继承所有属性
interface UserItem {
  name: string
  age: number
  enjoyFoods: string[]
  friendList: UserItem[]
}

// 这里继承了 UserItem 的所有属性类型,并追加了一个权限等级属性
interface Admin extends UserItem {
  permissionLevel: number
}
const admin: Admin = {
  name: 'Petter',
  age: 18,
  enjoyFoods: ['rice', 'noodle', 'pizza'],
  friendList: [
    {
      name: 'Marry',
      age: 16,
      enjoyFoods: ['pizza', 'ice cream'],
      friendList: [],
    },
    {
      name: 'Tom',
      age: 20,
      enjoyFoods: ['chicken', 'cake'],
      friendList: [],
    }
  ],
  permissionLevel: 1,
}
//5.2继承部分属性
interface UserItem {
  name: string
  age: number
  enjoyFoods: string[]
  friendList?: UserItem[]
}
// 这里在继承 UserItem 类型的时候,删除了两个多余的属性
interface Admin extends Omit<UserItem, 'enjoyFoods' | 'friendList'> {
  permissionLevel: number
}
// 现在的 admin 就非常精简了
const admin: Admin = {
  name: 'Petter',
  age: 18,
  permissionLevel: 1,
}

Symbol类型

  • 场景:一个对象中可以有两个相同属性名

  • 特点:是JS ES6推出的一个概念,通过class定义一个对象模板
  • 定义类

    • class User{}
    • 类与类之间可继承
    • 一个接口可继承类
  • 继承时,删除多余的属性:Omit<类名,'属性1'|'属性2'|'方法名'>
 //1. 定义一个类
class User {
  // constructor 上的数据需要先这样定好类型
  name: string
  // 入参也要定义类型
  constructor(userName: string) {
    this.name = userName
  }
  getName() {
    console.log(this.name)
  }
}
// 通过 new 这个类得到的变量,它的类型就是这个类
const petter: User = new User('Petter')
petter.getName() // Petter

//2. 类与类之间的继承
// 这是一个基础类
class UserBase {
  name: string
  constructor(userName: string) {
    this.name = userName
  }
}
// 这是另外一个类,继承自基础类
class User extends UserBase {
  getName() {
    console.log(this.name)
  }
}
// 这个变量拥有上面两个类的所有属性和方法
const petter: User = new User('Petter')
petter.getName()

//3. 一个接口继承类
// 这是一个类
class UserBase {
  name: string
  constructor(userName: string) {
    this.name = userName
  }
}
// 这是一个接口,可以继承自类
interface User extends UserBase {
  age: number
}
//接口继承类的时候也可以去掉类上面的方法
interface User extends Omit<UserBase, 'getName'> {
  age: number
}
// 这样这个变量就必须同时存在两个属性
const petter: User = {
  name: 'Petter',
  age: 18,
}

联合类型

  • 特点:一个变量可能出现多种类型,可以用联合类型定义
  • 定义:用|分隔类型,一般使用模板字符串(反引号)
// 可以在 demo 里运行这段代码
function counter(count: number | string) {
  console.log(`The current count is: ${count}.`)
}
// 不论传数值还是字符串,都可以达到我们的目的
counter(1)  // The current count is: 1.
counter('2')  // The current count is: 2.
  • 场景:

    • 通过路由实例判断是否符合要求的页面(vue的路由在不同数据结构里也有不同类型)
    • 涉及子组件或DOM操作,当还没有渲染出来获取的是null,渲染后才拿到组件或DOM结构
// 注意:这不是完整的代码,只是一个使用场景示例
import type { RouteRecordRaw, RouteLocationNormalizedLoaded } from 'vue-router'
function isArticle(
  route: RouteRecordRaw | RouteLocationNormalizedLoaded
): boolean {
  // ...
}

// querySelector 拿不到 DOM 的时候返回 null
const ele: HTMLElement | null = document.querySelector('.main')

任意值(any类型)

  • 特点:入参可以是多种不同类型
  • 注意点:一旦使用了 any ,代码里的逻辑请务必考虑多种情况进行判断或者处理兼容。
// 这段代码在 TS 里运行会报错
function getFirstWord(msg) {
  console.log(msg.split(' ')[0])
}
getFirstWord('Hello World')
getFirstWord(123)

//将隐式的改为显式
// 这里的入参显式指定了 any
function getFirstWord(msg:  any) {
  // 这里使用了 String 来避免程序报错
  console.log(String(msg).split(' ')[0])
}
getFirstWord('Hello World')
getFirstWord(123)

unknown类型

  • 特点:用于描述不确定变量

void类型

  • 特点:一个函数没有返回值,其返回值是void类型

never类型

  • 特点:表示函数永远不会发生值的类型
  • 场景:函数是个死循环或抛出一个异常

tuple类型

  • 特点:元组中每个元素都有自己特性的类型,根据索引值获取的值可确定对应类型
  • 场景:tuple作为返回的值

函数

  • 特点:是JS中重要成员之一,所有功能的实现都是基于函数
函数的基本写法
// 注意:这是 TypeScript 代码

// 写法一:函数声明
function sum1(x: number, y: number): number {
  return x + y
}

// 写法二:函数表达式
const sum2 = function(x: number, y: number): number {
  return x + y
}

// 写法三:箭头函数
const sum3 = (x: number, y: number): number => x + y

// 写法四:对象上的方法
const obj = {
  sum4(x: number, y: number): number {
    return x + y
  }
}

// 还有很多……
函数的可选参数
  • 注意点:可选参数必须在必传参数的后面
  • 补充:可选类型可做 类型 和undefined的联合类型
// 注意 isDouble 这个入参后面有个 ? 号,表示可选
function sum(x: number, y: number, isDouble?: boolean): number {
  return isDouble ? (x + y) * 2 : x + y
}

// 这样传参都不会报错,因为第三个参数是可选的
sum(1, 2) // 3
sum(1, 2, true) // 6

无返回值的函数
  • 特点:无返回值的函数用void定义(如表单验证)
  • 注意点:

    • void和null/undefined不可混用
    • 返回值类型是null需要return一个null值
// 注意这里的返回值类型
function sayHi(name: string): void {
  console.log(`Hi, ${name}!`)
}

sayHi('Petter') // Hi, Petter!

// 只有返回 null 值才能定义返回类型为 null
function sayHi(name: string): null {
  console.log(`Hi, ${name}!`)
  return null
}

function sayHi(name: string): void {
  // 这里判断参数不符合要求则提前终止运行,但它没有返回值
  if (!name) return

  // 否则正常运行
  console.log(`Hi, ${name}!`)
}
异步函数的返回值
  • 定义:用Promise定义其返回值,T是指泛型
  • 说明:resolve中是 一个字符串,所以它的返回类型是 Promise<string> (假如没有 resolve 数据,那么就是 Promise<void>
// 注意这里的返回值类型
function queryData(): Promise<string> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Hello World')
    }, 3000)
  })
}

//没有resolve数据
function queryData(): Promise<void> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve()
    }, 3000)
  })
}

queryData().then((data) => console.log(data))
函数本身的类型
  • 特点:

    • 函数名没有指定类型,TS会根据函数体自动推导
    • // 这里的 sum ,确实是没有指定类型
      const sum = (x: number, y: number): number => x + y
      
    • 完整的函数写法
    • const sum: (x: number, y: number) => number = (x: number, y: number): number =>
        x + y
      
      //const sum: (x: number, y: number) => number 是这个函数的名称和类型
      //= (x: number, y: number) 这里是指明了函数的入参和类型
      //: number => x + y 这里是函数的返回值和类型
      
    • 需显式写出函数的类型:给对象定义方法
    • // 对象的接口
      interface Obj {
        // 上面的方法就需要显式的定义出来
        sum: (x: number, y: number) => number
      }
      
      // 声明一个对象
      const obj: Obj = {
        sum(x: number, y: number): number {
          return x + y
        }
      }
      
函数的重载(明确入参与返回的类型一致)
  • 场景

    • 当监听单个数据源时,它匹配了类型 1 ,当传入一个数组监听多个数据源时,它匹配了类型 2
  • 使用:向声明原始数据类型,在声明引用类型(如,数组)
 //1. 函数的基本写法
// 写法一:函数声明
function sum1(x: number, y: number): number {
  return x + y
}
// 写法二:函数表达式
const sum2 = function(x: number, y: number): number {
  return x + y
}
// 写法三:箭头函数
const sum3 = (x: number, y: number): number => x + y
// 写法四:对象上的方法
const obj = {
  sum4(x: number, y: number): number {
    return x + y
  }
}

// 还有很多……


//2. 函数的可选参数
// 注意 isDouble 这个入参后面有个 ? 号,表示可选
function sum(x: number, y: number, isDouble?: boolean): number {
  return isDouble ? (x + y) * 2 : x + y
}
// 这样传参都不会报错,因为第三个参数是可选的
sum(1, 2) // 3
sum(1, 2, true) // 6

//3. 无返回值的函数
// 注意这里的返回值类型
function sayHi(name: string): void {
  console.log(`Hi, ${name}!`)
}
sayHi('Petter') // Hi, Petter!

注意点:voidnullundefined 不可以混用,
若函数返回值类型是 null ,需要 return 一个 null// 只有返回 null 值才能定义返回类型为 null
function sayHi(name: string): null {
  console.log(`Hi, ${name}!`)
  return null
}
function sayHi(name: string): void {
  // 这里判断参数不符合要求则提前终止运行,但它没有返回值
  if (!name) return
  // 否则正常运行
  console.log(`Hi, ${name}!`)
}

//4. 异步函数的返回值
// resolve 一个字符串,所以它的返回类型是 Promise<string> 
//(假如没有 resolve 数据,那么就是 Promise<void> )。
// 注意这里的返回值类型
function queryData(): Promise<string> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Hello World')
    }, 3000)
  })
}

queryData().then((data) => console.log(data))

//5. 函数本身的类型

//6. 函数的重载
// 这一次用了函数重载
function greet(name: string): string  // TS 类型
function greet(name: string[]): string[]  // TS 类型
function greet(name: string | string[]) {
  if (Array.isArray(name)) {
    return name.map((n) => `Welcome, ${n}!`)
  }
  return `Welcome, ${name}!`
}

// 单个问候语,此时只有一个类型 string
const greeting = greet('Petter')
console.log(greeting) // Welcome, Petter!

// 多个问候语,此时只有一个类型 string[]
const greetings = greet(['Petter', 'Tom', 'Jimmy'])
console.log(greetings)
// [ 'Welcome, Petter!', 'Welcome, Tom!', 'Welcome, Jimmy!' ]

npm包

可能会报...implicitly has an 'any' type类似的错误信息,需在安装原包名前加@types

如:npm install -D @types/md5

类型断言

  • 语法

    • 值 as 类型
    • <类型>值
  • 常用的场景

    • 特点:一个变量使用联合类型,若不显式指出类型可能会导致运行报错
    • 重载
// 对单人或者多人打招呼
function greet(name: string | string[]): string | string[] {
  if (Array.isArray(name)) {
    return name.map((n) => `Welcome, ${n}!`)
  }
  return `Welcome, ${name}!`
}

// 虽然已知此时应该是 string[]
// 但 TypeScript 还是会认为这是 string | string[]
const greetings = greet(['Petter', 'Tom', 'Jimmy'])

// 会导致无法使用 join 方法(因为 string 类型不具备 join 方法)
const greetingSentence = greetings.join(' ')
console.log(greetingSentence)
  • 注意点:类型断言让TS不检查代码(所以只在能够确保代码正确的情况下去使用它)

    • 反例子
    • // 原本要求 age 也是必须的属性之一
      interface User {
        name: string
        age: number
      }
      
      // 但是类型断言过程中,遗漏了
      const petter = {} as User
      petter.name = 'Petter'
      
      // TypeScript 依然可以运行下去,但实际上的数据是不完整的
      console.log(petter) // { name: 'Petter' }
      

类型推论