【学】通俗易懂的 TypeScript(一)

231 阅读2分钟

TypeScript 是什么

TypeScript

TypeScript: JavaScript With Syntax For Types. (typescriptlang.org)

TypeScript中文网 · TypeScript——JavaScript的超集 (tslang.cn)

TypeScript是JavaScript的超集,是一个可选的、静态的类型系统

在JS的基础上,增加的是类型系统。2012年微软发布。开源、拥抱ES标准。

  1. 类型系统

对代码中所有的标识符(变量、函数、参数、返回值)进行类型检查。

  1. 可选的

学习曲线非常平滑。

  1. 静态的(在运行之前)

类型检查发生的时间,在编译的时候,而非运行时,TS不参与任何运行时的类型检查。

  1. 安装tsc: ts编译器

无论是浏览器环境,还是node环境,无法直接识别ts代码,tsc: ts ==> es

$ npm i -g typescript
Version 4.6.4

默认情况下,TS会做出下面几种假设:

假设当前的执行环境是dom

如果代码中没有使用模块化语句(import、export),便认为该代码是全局执行

编译的目标代码是ES3

  • 有两种方式更改以上默认情况

    • 使用tsc命令行的时候,加上选项参数。
    • 使用ts配置文件,更改编译选项。

TS的配置文件

  1. 配置文件初始化,命令行执行tsc --init,当前路径下会生成tsconfig.json
{
  "compilerOptions": {
    //编译选项
    "target": "es2016", //配置编译目标代码的版本标准
    "module": "es6", //配置编译目标使用的模块化标准
    "lib": ["es2016"], // 开发环境
    "outDir": "./dist"
  },
  "include": ["./src"] // 设置需要进行编译的文件,支持路径模式匹配;
}
  • src/index.ts
import num from './test'const a: string | number = num
​
  • src/test.ts
const num = 999
export default num
  • 命令行执行tsc

dist 目录生成了编译后的代码。

image.png

  1. 开发环境中配置node环境 npm i -D @types/node

@types 是一个ts官方的类型库,其中包含了很多对JS代码的类型描述。

注意:

使用了配置文件后,使用tsc进行编译时,不能跟上文件名,如果跟上文件名,会忽略配置文件

image.png

tsconfig.json

  • 重要选项
files - 设置要编译的文件的名称;
include - 设置需要进行编译的文件,支持路径模式匹配;
exclude - 设置无需进行编译的文件,支持路径模式匹配;
compilerOptions - 设置与编译流程相关的选项。
  • compilerOptions 选项
{
  "compilerOptions": {
​
    /* 基本选项 */
    "target": "es5",                       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs",                  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],                             // 指定要包含在编译中的库文件
    "allowJs": true,                       // 允许编译 javascript 文件
    "checkJs": true,                       // 报告 javascript 文件中的错误
    "jsx": "preserve",                     // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,                   // 生成相应的 '.d.ts' 文件
    "sourceMap": true,                     // 生成相应的 '.map' 文件
    "outFile": "./",                       // 将输出文件合并为一个文件
    "outDir": "./",                        // 指定输出目录
    "rootDir": "./",                       // 用来控制输出目录结构 --outDir.
    "removeComments": true,                // 删除编译后的所有的注释
    "noEmit": true,                        // 不生成输出文件
    "importHelpers": true,                 // 从 tslib 导入辅助工具函数
    "isolatedModules": true,               // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).
​
    /* 严格的类型检查选项 */
    "strict": true,                        // 启用所有严格类型检查选项
    "noImplicitAny": true,                 // 在表达式和声明上有隐含的 any类型时报错
    "strictNullChecks": true,              // 启用严格的 null 检查
    "noImplicitThis": true,                // 当 this 表达式值为 any 类型的时候,生成一个错误
    "alwaysStrict": true,                  // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
​
    /* 额外的检查 */
    "noUnusedLocals": true,                // 有未使用的变量时,抛出错误
    "noUnusedParameters": true,            // 有未使用的参数时,抛出错误
    "noImplicitReturns": true,             // 并不是所有函数里的代码都有返回值时,抛出错误
    "noFallthroughCasesInSwitch": true,    // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)
​
    /* 模块解析选项 */
    "moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
    "baseUrl": "./",                       // 用于解析非相对模块名称的基目录
    "paths": {},                           // 模块名到基于 baseUrl 的路径映射的列表
    "rootDirs": [],                        // 根文件夹列表,其组合内容表示项目运行时的结构内容
    "typeRoots": [],                       // 包含类型声明的文件列表
    "types": [],                           // 需要包含的类型声明文件名列表
    "allowSyntheticDefaultImports": true,  // 允许从没有设置默认导出的模块中默认导入。
​
    /* Source Map Options */
    "sourceRoot": "./",                    // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
    "mapRoot": "./",                       // 指定调试器应该找到映射文件而不是生成文件的位置
    "inlineSourceMap": true,               // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
    "inlineSources": true,                 // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
​
    /* 其他选项 */
    "experimentalDecorators": true,        // 启用装饰器
    "emitDecoratorMetadata": true          // 为装饰器提供元数据的支持
  }
}

基本类型约束

基本类型

  1. number
const num: number = 100
  1. string

  2. boolean

  3. null、undefined

nullundfined 是所有其他类型的子类型,它们可以赋值给其他类型。

通过添加strictNullChecks: true ,可以获得更严格的空类型检查,nullundefinde 只能赋值给自身。

  1. 数组
const arr: number[] = [3, 4, 5]
  1. object
const oK: object = {
  name: 'Kobe',
  No: '24'
}

其他类型

  1. 类型别名
type Gender = {male: '男'; female: '女'}

const gender: Gender = {
  male: '男',
  female: '女'
}
  1. 联合类型

多种类型任选其一,配合类型保护进行判断。

类型保护: 当对某个变量进行类型判断之后,在判断的语句块中便可以确定他的确切类型。typeof 可以触发类型保护。

const user: string | undefined = 'Kobe'
  1. void 类型

通常用于约束函数的返回值,表示该函数没有任何返回。

const fn = (): void => {}
  1. never 类型

通常用于约束函数的返回值,表示该函数永远不可能结束。

const fn = (): never => {
  throw new Error()
}
  1. 元组类型(Tuple)

一个固定长度的数据,并且数据中的每一项的类型确定。

// 元祖的表示和数组非常类似,只不过它将类型写在了里面 这就对每一项起到了限定的作用
let user: [string, number] = ['viking', 20]
  1. any类型

any类型可以绕过类型检查,因此,any类型的数据可以赋值给任意类型。

  1. 字面量类型

使用一个值进行约束。

type No = 24

const num: No = 24 // 只能为24

函数的相关约束

  1. 函数重载

在函数实现之前,对函数调用的多种情况进行声明。

function sum(a: number, b: number): number
function sum(a: string, b: string): string
function sum(a: number | string, b: number | string) {
  if (typeof a == 'number' && typeof b == 'number') {
    return a + b
  }
  if (typeof a == 'string' && typeof b == 'string') {
    return a + b
  }
}

console.log(sum(1, 2)) // 3
console.log(sum('a1', 'a2')) // a1a2
  1. 可选参数

可以在某些参数名后加上问好,表示该参数可以不用传递。可选参数必须在参数列表的末尾。

function sum(a: number | string, b: number | string, c?: number) {
  if (typeof a == 'number' && typeof b == 'number') {
    return a + b + c
  }
  if (typeof a == 'string' && typeof b == 'string') {
    return a + b + c
  }
}

console.log(sum(1, 2, 3)) // 6
console.log(sum('a1', 'a2')) // a1a2undefined

扩展类型

什么是扩展类型?

自己开发的类型。

扩展类型:类型别名、枚举、接口、类

枚举

  1. 定义

枚举会出现在编译结果中,编译结果中表现为对象

enum 枚举名 {
  字段1=值1
  字段2=值2
}
  1. 规则

枚举的字段值可以是字符串或数字数字枚举的值会自动自增

被数字枚举约束的变量,可以直接赋值为数字。

数字枚举的编译结果和字符串枚举有差异。

  1. 最佳实践

尽量不要在一个枚举中即出现字符串字段,又出现数字字段。

使用枚举时,尽量使用枚举字段的名称,而不是用真实的值。

enum Gender {
  male = '男',
  female = '女'
}

let gender: Gender = Gender.male
gender = Gender.female

interface

TypeScript 的接口:用于约束类,对象、函数的标准。

  1. 接口约束对象
interface User {
  name: string
  age: number
}

let user: User = {name: 'oK', age: 11}
  1. 约束函数
  • 对象中的函数
interface User {
  name: string
  sayHello: () => void
}

let user: User = {
  name: 'oK',
  sayHello: () => {}
}
  • 直接约束函数
interface conditon {
  //定义函数的参数类型和返回值类型
  (n: number): boolean
}
function sum(numbers: number[], callBack: conditon) {}

const callback = (n: number) => {
  return true // 返回非 boolean类型,会报错提醒
}

sum([1, 2], callback)

  1. 接口可以继承
  • 可以通过接口之间的继承,实现多种接口的组合
interface A {
  name: string
}
interface B {
  number: number
}

interface C extends A, B {}

const a: C = {
  name: 'oA',
  number: 9 // C继承了A、B的属性约束
}
  • 使用类型别名可以实现类似的 组合效果,需要通过 & ,他叫做交叉类型
type A = {name: string}
type B = {number: number}
type C = A & B

const a: C = {
  name: 'oA',
  number: 9 // 使用交叉类型
}
  • 他们的区别

    • 定义基本类型别名

type可以定义基本类型别名, 但是interface无法定义,如:

type userName = string
const user: userName = 'mj'
    • 声明合并

如果你多次声明一个同名的接口,TypeScript 会将它们合并到一个声明中,并将它们视为一个接口。这称为声明合并, 例如:

interface Person {
  name: string
}
interface Person {
  age: number
}

let user: Person = {
  name: 'Tolu',
  age: 0
}

这种情况下,如果是type的话,重复使用Person是会报错的:

type Person { name: string }; 

// Error: 标识符“Person”重复。ts(2300)
type Person { age: number }
  1. readonly修饰符

只读修饰符,修饰的目标是只读的。

不在编译结果中。

interface User {
  readonlyid: string
  name: string
  readonlyarr: readonly string[] // 限制arr 不能重新赋值和不能改变。
}

const arr: readonly number[] = [3, 4, 5]
// Error: 类型“readonly number[]”上不存在属性“push”。ts(2339)
arr.push(999)

TS中的类

  1. 属性列表的方式
class Animal {
  name: string
  age: number

  constructor(name: string, age: string) {
    this.name = name

    // Error: 不能将类型“string”分配给类型“number”。ts(2322)
    this.age = age
  }
}
  1. 属性的初始化检查

更加严格的方式检查属性是不是初始化。

属性的初始化位置: 构造函数中、属性默认值

"stricPropertyInitialization": true
  1. 类成员的访问修饰符

public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的。

private 修饰的属性或方法是私有的,不能在声明它的类的外部访问。

protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的。

  1. 访问器

getter && setter

class Parent {
  private _name: string
  get name(): string {
    return this._name
  }
  set name(name: string) {
    console.log(`name设置前: ${this._name} 设置后: ${name}`)
    this._name = name
  }
}
const parent = new Parent()
parent.name = 'ok' // 可以直接使用赋值语句,但是会自动调用set name(name: string)方法
  1. 类与接口

类实现一个接口。

泛型

泛型是指附属于函数、类、接口、类型别名之上的类型。

泛型相当于一个类型变量,在定义是,无法预先知道具体的类型,可以用该变量来代替,只有到调用时,才能确定他的类型。

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

  1. 函数中使用泛型

在函数名之后写上 <泛型名称>

function echo<T>(arg: T): T {
  return arg
}

const num = echo<number>(123)
console.log(typeof num) // number

const str = echo<string>('123')
console.log(typeof str) // string
  1. 类、接口、类型别名中使用泛型

直接在名称后面写上 <泛型名称>

  • 类中使用
class Queue<T> {
  private data = []
  push(item: T) {
    return this.data.push(item)
  }
  pop(): T {
    return this.data.shift()
  }
}

const queue = new Queue<number>()
queue.push(1)
// Error: 类型“string”的参数不能赋给类型“number”的参数。ts(2345)
queue.push('1')
  • interface 中使用
interface KeyPair<T, U> {
  key: T
  value: U
}

let kp1: KeyPair<number, string> = {key: 1, value: 'str'}
let kp2: KeyPair<string, number> = {key: 'str', value: 123}
  1. 泛型约束

用于限制泛型的取值

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法。

interface IWithLength {
  length: number
}

function echoWithLength<T extends IWithLength>(arg: T): T {
  console.log(arg.length)
  return arg
}

echoWithLength('str') // 3
echoWithLength({length: 10}) // 10
echoWithLength([1, 2, 3]) // 3
  1. 多泛型
// 泛型也可以传入多个值
function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]]
}
const result = swap<string, number>(['string', 123])
console.log(result)