跳至主要內容

TypeScript笔记

Mr.Chen前端学习笔记TypeScript大约 16 分钟约 4700 字

TypeScript 学习笔记

TypeScript 的类型校验是给程序员看的,在编译后不会存在 TS 代码。

类型注解

function fn(person: string): void {
  // 参数类型是字符串,没有返回值
  ///...
}
fn('str') // 如传递参数非字符串,vscode编辑器中或在编译时将给出错误提示

const test: number = 1

有哪些基础类型注解?

// 基础类型
:string
:number
:boolean

:null  // 只能是null值
:undefined // 只能是undefined值

:symbol

// 引用类型
:object // 不常用,多使用接口来给对象进行类型注解

// 其他
:any  // 任意类型
:void  // 空,用于函数的无返回值的注解
:never // 用于标注函数代码永远执行不完(如抛出错误的函数,死循环函数)


什么是类型注解和类型推断、类型断言?

类型注解 是显式的标注类型

类型推断 是编辑器根据值来自动推断出类型 (编辑器中鼠标移到变量会显示类型的提示)。

类型断言 是告诉编译器,“相信我,它就是这个类型”。

// 类型注解就是显式的写出类型
const myNumber: number = 123

// 类型推断是根据值来推断类型 (鼠标移到变量会显示类型(或直接显示值)的提示)
const myString = 'test'

// 类型断言(开发者明确知道是什么类型)
const someValue: any = 'abc'
const strLength: number = (someValue as string).length // 断言someValue是字符串类型

类型断言

const el = document.querySelector('#img')
;(el as HTMLImageElement).src = 'xx' // 断言el就是图片元素类型

// 对象内的类型断言
type TestObj = {
  a: string
}
const obj = {
  test: <TestObj>{
    a: 'aaa'
  }
}

// 或者
const obj = {
  test: {
    a: 'aaa'
  } as TestObj
}

对象的类型

// 对象字面量的类型检查
const xiaojiejie: {
  name: string
  age: number
} = {
  name: '小红',
  age: 18
}

// 标注:object
const obj: object = {}

// 使用接口
interface Person {
  name: string
  age: number
}
const xjj: Person = {
  name: 'xh',
  age: 18
}

// class类的类型检查
class Parson {}
const xiaobai: Parson = new Parson()

// 函数和返回值的类型检查
const fn: () => string = () => '123'

函数的类型注解

// 函数返回值的类型注解 fn(): number {}
function getTotal2(one: number, two: number): number {
  return one + two
}
getTotal2(1, 2)

// 无返回值: void
function sayHello(): void {
  console.log('Hello')
}

// 永远执行不玩的函数
function setTimer(): never {
  throw new Error()
  console.log(123)
}

// 参数是对象时的注解 (也可以用接口)
function add({ one, two }: { one: number; two: number }) {
  return one + two
}

const total = add({ one: 1, two: 2 })

// 类型别名方式定义函数类型
type Callback = (a: string) => string
let fn: Callback = a => ''

// 接口方式定义函数类型
interface ICallBack {
  (a: string): string
}
let fn1: ICallBack = a => ''

函数中的 this 类型

interface TestObj {
  a: number
  fn: (x: number) => void
}

// 普通函数的this
let obj: TestObj = {
  a: 1,
  fn(this: TestObj, x: number) {
    // 注意这里的this非参数,只是给this类型注解。x还是第一个参数
    this.a
  }
}

// 箭头函数的this (this是固定指向当前声明函数所在的作用域)
let obj2: TestObj = {
  a: 1,
  fn(this: TestObj, x: number) {
    return () => {
      this.a
    }
  }
}

函数重载 (函数参数间的组合约定)

原生 js 中并没有真正的函数重载,重名函数会被覆盖,但 ts 中可以有。

浅谈 JavaScript 函数重载open in new window

ts 支持函数重载,一般用于针对传入不同的参数或参数数量以及返回值之间的类型约定。

// 第一套约定(attr是'display'时,value必须是 'block' | 'nonde')
function showOrHide(ele: HTMLElement, attr: 'display', value: 'block' | 'nonde');
// 第二套约定(attr是'opacity'时,value必须是number)
function showOrHide(ele: HTMLElement, attr: 'opacity', value: number);
// 函数的实现
function showOrHide(ele: HTMLElement, attr: any, value: any) {
  // ...
}

// interface也可以声明函数重载
interface Fn{
  (name: string): string
  (name: number): number
}
const fn: Fn= () =>

更多 ts 函数重载知识open in new window

数组的类型注解

const numberArr: number[] = [1, 2, 3]
const stringArr: string[] = ['a', 'b']
const undefinedArr: undefined[] = [undefined, undefined]
const arr: (number | string | boolean)[] = [1, 'a', true, false] // 用到联合类型

// 类型别名 type alias
type lady = { name: string; age: number }

const xiaojj: lady[] = [
  {
    name: 'xiaojj',
    age: 90
  },
  {
    name: 'j',
    age: 30
  }
]

// 或用类的方式
class Ady2 {
  name: string
  age: number
}

const xiaojj2: Ady2[] = [
  {
    name: 'xiaojj',
    age: 90
  },
  {
    name: 'j',
    age: 30
  }
]

元组

元组,可以理解为:已知元素数量和类型的数组

// 联合类型
const xjj: (string | number)[] = ['a', 22, 'b'] // 规定整个数组当中可以有string或number

// 元组注解 注意这里的注解只有一个中括号
const xjj1: [string, number, number] = ['a', 22, 33] // 规定了数组每个元素对应位置的类型

// Note: 在开发中元祖的使用在相对少

// CSV的方式定义数据格式; (二维数组时需要多加一个中括号)
const xjj2: [string, number, number][] = [
  ['a', 22, 33],
  ['a', 22, 33]
]

接口

接口,可以理解为对象属性的类型描述。和类型别名类似,不同的是 接口必须是一个对象,而别名可以直接是一个类型,如 type Girl = string

interface Girl {
  // 接口 (理解:对象属性的类型描述)
  readonly name: string // 只读属性 (定义之后不能再修改)
  age: number
  waistline?: number // 加个问号表示是可选值
  [propname: string]: any // 表示可以有不限制属性名的属性,但属性名需要是字符串,值可以是任意类型
  say(): string // 函数类型,返回值是string (如无返回值时是 void)。  say()也可以加入参数类型检查,如say(p:number)
}
// 和类型别名类似,不同的是 接口必须是一个对象,而别名可以直接是一个类型,如 type Girl = string

// 接口的继承
interface Teacher extends Girl {
  teach(): string
}

const girl = {
  name: '大脚',
  age: 18,
  sex: '女',
  say() {
    return '欢迎光临'
  },
  teach() {
    return '教'
  }
}

const screenResume = ({ name, age, bust, sex }: Girl) => {
  console.log(name, age, bust, sex)
}
const getResume = ({ name, age, bust, teach }: Teacher) => {
  teach()
  console.log(name, age, bust)
}
screenResume(girl)
getResume(girl)

// 接口在class类中的使用
class xiaojjj implements Girl {
  name = 'xiaojj'
  age = 18
  bust = 98
  sex = '女'
  say() {
    return '欢迎光临'
  }
}

可索引的类型

interface Arr {
  [index: number]: string // 表示通过索引访问数据时返回的类型是string
}

const myArr: Arr = ['1', '2', '3']
const myStr: string = myArr[1]

ES6 class 类中应用 TS

class 类的修饰器

// 类的修饰器:
// public (公共) 允许内外部使用
// protected (受保护的) 允许在内部和继承内使用
// private (私有) 允许在内部使用,但不能在继承中使用

// 类的内部和外部,{}内属于内部,外面是外部
class Person {
  // public 内外都可以使用,可省略,不写时默认public。 protected只允许内部使用
  name: string // 这里的string注释是TS使用的
  private age: 18
  public sayHello() {
    console.log(this.name + this.age + 'say hello')
  }
}

class Teacher2 extends Person {
  public sayBye() {
    console.log(this.name + ' say bye') // protected 可以在继承中使用到
  }
}

const person = new Person()
person.name = 'test' // 在类的外部定义
console.log(person.name)

class 类的构造函数中使用类型校验

class Person2 {
  constructor(public name: string) {
    this.name = name
  }
}

class Teacher3 extends Person2 {
  constructor(public age: number) {
    super('test-name') // 这里传的值是给Person2的构造函数。即使父类没有构造函数,子类的构造函数内也要写super()
  }
}
const teacher3 = new Teacher3(18)
console.log(teacher3.name)
console.log(teacher3.age)

class 类的 getter、setter 和 static

class Xjj {
  constructor(private _age: number) {}
  get age() {
    return this._age - 10
  } // 访问器属性,以属性的形式访问age,并可以对属性进行包装
  set age(age: number) {
    this._age = age + 3
  }
}

const dj = new Xjj(28)
dj.age = 25

console.log(dj.age)

// 静态属性 static。  静态属性指不需要通过实例化,直接通过Girl.prop的方式就可以访问到属性
class Girl {
  static sayLove() {
    return 'I love you'
  }
}

// const girl = new Girl()
console.log(Girl.sayLove())

class 类的只读属性

// 只读属性
class Person {
  public readonly _name: string // 只读属性
  constructor(name: string) {
    this._name = name
  }
}

const person = new Person('testName')
// person._name = '222'; // 不能修改只读属性
console.log(person._name)

抽象类

/ 抽象类
abstract class Girls {
  abstract skill(); // 注意这里只是定义抽象方法,而不具有方法的实现
}

class Waiter extends Girls{ // 继承了抽象类之后要 实现抽象类内的成员
  skill() {
    console.log('大爷1')
  }
}

class BaseTeacher extends Girls{
  skill() {
    console.log('大爷2')
  }
}

class SeniorTeacher extends Girls{
  skill() {
    console.log('大爷3')
  }
}
// abstract关键字标注为抽象类(没有具体实现,不能用new实例化。子类继承该抽象类必须实现相应方法,用于规范子类)
abstract class Component<T1, T2> {
  props: T1
  state: T2

  constructor(props: T1) {
    this.props = props
  }

  // 用abstract标注为抽象类的方法,不能有具体实现
  abstract render(): string
}

// 规范类的props和state
interface Props {
  val: number
}
interface State {
  x: number
}

// 规范类内部的方法
interface Log {
  getInfo(): string
}
interface Save {
  save(): void
}

// <Props, State>通过泛型传入类型. implements 关键字使当前类必须要履行 Log,Storage 接口内定于的契约
class MyComponent extends Component<Props, State> implements Log, Save {
  constructor(props: Props) {
    super(props)
    this.state = {
      x: 1
    }
  }

  render() {
    // this.props.val
    // this.state.x
    return '<MyComponent>'
  }

  getInfo(): string {
    return ''
  }
  save() {}
}

const myComponent = new MyComponent({ val: 1 })
myComponent.render()

/**
 * 类中的接口使用implements关键字
 * 1. 如果一个类implements了一个接口,那么就必须实现该接口中定义的契约
 * 2. 多个接口使用逗号分隔
 * 3. implements与extends可以同时存在
 */

/**
 * TS中类和接口知识点
 * 1. 抽象类在编译后会产生实体代码,接口不会
 * 2. TS只支持继承,即一个子类之能有一个父类
 * 3. 接口不能有实现,抽象类可以有属性的实现,没有方法实现
 */

联合类型和类型保护

联合类型 指某个参数可以是多种类型。

类型保护 指参数属于某个类型才有相应的操作。

interface Waiter {
  anjiao: boolean
  say: () => {}
}

interface Teacher {
  anjiao: boolean
  skill: () => {}
}

function judgeWho(animal: Waiter | Teacher) {
  // 联合类型
  // 第一种断言方法
  if (animal.anjiao) {
    // (animal as Teacher) 的意思是:断言 animal 是 Teacher类型
    ;(animal as Teacher).skill()
  } else {
    ;(animal as Waiter).say()
  }

  // 第二种断言方法
  if ('skill' in animal) {
    animal.skill()
  } else {
    animal.say()
  }

  // 第三种类型保护方法是使用typeof来判断 (代码省略)
}

class NumberObj {
  count: number
}
function addObj(first: object | NumberObj, second: object | NumberObj) {
  // 联合类型
  if (first instanceof NumberObj && second instanceof NumberObj) {
    // 类型保护
    return first.count + second.count
  }
  return 0
}

枚举

// Enum枚举类型   (个人理解枚举:约定一组可选的常量。 使用常量名表示某个值的含义,增强可读性。)

// js写法
// const Status = {
//   MASSAGE: 0,
//   SPA: 1,
//   DABAOJIAN: 2
// }

// ts写法
enum Status {
  MASSAGE, // 如果想从1开始,给MASSAGE = 1, 即可
  SPA,
  DABAOJIAN
} // 默认赋值 0 、1、2

console.log(Status.MASSAGE, Status[0]) // 0, MASSAGE  可以通过下标反查

function getStatus(status: any) {
  if (status === Status.MASSAGE) {
    return 'massage'
  } else if (status === Status.SPA) {
    return 'spa'
  } else if (status === Status.DABAOJIAN) {
    return 'dabaojian'
  }
}

const result = getStatus(Status.SPA)
console.log(result)

泛型

泛型,最简单的理解:泛指的类型。(类似函数中的形参与实参)

函数中的泛型使用

// function join(first: string | number, second: string | number) {
//   return `${first}${second}`
// }
// join('jspang', 1); // 如果我想第一个参数是字符串,第二个也必须是字符串,这么就用到泛型

// 泛型使用,如同定义形参,在调用时指定类型
function join<JSPang>(first: JSPang, second: JSPang) {
  return `${first}${second}`
}

join<string>('jspang', '123')
join<number>(11, 22)

// 泛型中数组的使用
function myFun<ANY>(params: ANY[]) {
  // ANY[] or Array<ANY>
  return params
}
myFun<string>(['a', 'b'])

// 两个类型参数的使用(工作中,常用T表示泛型)
function join2<T, P>(first: T, second: P) {
  return `${first}${second}`
}

join2<string, number>('jspang', 123)
join2<number, string>(11, '22')
join2(11, '22') // 泛型也支持类型推断 (鼠标移到函数名有提示)

class 类中使用泛型

// class SelectGirl {
//   constructor(private girls: string[] | number[]) { } // private 私有的参数,外部无法修改
//   getGirl(index: number): string | number {
//     return this.girls[index]
//   }
// }

// 使用泛型
class SelectGirl<T> {
  // 泛型的约束: <T extends number | string>
  constructor(private girls: T[]) {} // private 私有的参数,外部无法修改
  getGirl(index: number): T {
    return this.girls[index]
  }
}

// const selectGirl = new SelectGirl<string>(['大脚', 'xiaohong', 'xiaobai'])
const selectGirl = new SelectGirl<number>([101, 102, 103])
console.log(selectGirl.getGirl(1))

// 泛型中的继承
interface Girl {
  name: string
}
class SelectGirl2<T extends Girl> {
  // 泛型T中必须有一个name属性,继承自Girl接口
  constructor(private girls: T[]) {} // private 私有的参数,外部无法修改
  getGirl(index: number): string {
    return this.girls[index].name
  }
}

const selectGirl2 = new SelectGirl2([
  { name: '大脚1' },
  { name: '大脚2' },
  { name: '大脚3' }
])

console.log(selectGirl2.getGirl(1))

类型保护

我们通常在 JavaScript 中通过判断来处理⼀些逻辑,在 TypeScript 中这种条件语句块还有另外⼀

个特性:根据判断逻辑的结果,缩⼩类型范围(有点类似断⾔),这种特性称为 类型保护 ,触发条

件:

  • 逻辑条件语句块:if、else、elseif

  • 特定的⼀些关键字:typeof、instanceof、in

typeof

function fn(a: string | number) {
  // error,不能保证 a 就是字符串
  a.substring(1)
  if (typeof a === 'string') {
    // ok
    a.substring(1)
  } else {
    // ok
    a.toFixed(1)
  }
}

instanceof

function fn(a: Date | Array<any>) {
  if (a instanceof Array) {
    a.push(1)
  } else {
    a.getFullYear()
  }
}

in

interface IA {
 x: string;
 y: string; }
interface IB {
 a: string;
 b: string; }
function fn(arg: IA | IB) {
 if ('x' in arg) {
 // ok
 arg.x;
 // error
 arg.a;
 } else {
 // ok
 arg.a;
 // error
字⾯量类型保护
如果类型为字⾯量类型,那么还可以通过该字⾯量类型的字⾯值进⾏推断
⾃定义类型保护
有的时候,以上的⼀些⽅式并不能满⾜⼀些特殊情况,则可以⾃定义类型保护规则
 arg.x;
 }
}

字面量类型保护

interface IA {
  type: 'IA'
  x: string
  y: string
}
interface IB {
  type: 'IB'
  a: string
  b: string
}
function fn(arg: IA | IB) {
  if (arg.type === 'IA') {
    // ok
    arg.x
    // error
    arg.a
  } else {
    // ok
    arg.a
    // error
    arg.x
  }
}

自定义类型保护

function canEach(data: any): data is Element[] | NodeList {
  return data.forEach !== undefined
}
function fn2(elements: Element[] | NodeList | Element) {
  if (canEach(elements)) {
    elements.forEach((el: Element) => {
      el.classList.add('box')
    })
  } else {
    elements.classList.add('box')
  }
}

data is Element[]|NodeList 是⼀种类型谓词,格式为: xx is XX ,返回这种类型的函数就可以

被 TypeScript 识别为类型保护

类型操作

typeof

获取数据的类型

let str = 'kkk'

let t = typeof str // 使用typeof获取str的类型,返回string给变量t (原生js的typeof使用)

type myType = typeof str // 使用typeof获取str的类型,返回string给类型myType (TS的typeof使用)

keyof

获取类型的所有key的集合

// 使用示例1
interface Person {
  name: string
  age: number
}

type PersonKeys = keyof Person
// 等同: type PersonKeys = 'name' | 'age'; // 注意是key的集合,不是值的集合
// 使用示例2
let p1 = {
  name: 'xx',
  age: 28
}

// keyof typeof p1 的作用,首先typeof将p1对象的类型取出,keyof再将类型的key集合取出,结果:'name' | 'age'
function getPersonVal(k: keyof typeof p1) {
  return p1[k]
}

in

内部使用for...in对类型进行遍历

interface Person {
  name: string
  age: number
}

type PersonKeys = keyof Person // 'name' | 'age'
type NewPerson = {
  [k in PersonKeys]: string
  // 也可写成 [k in 'name' | 'age']: string
  // 或 [k in keyof Person]: string
} // {name: string; age: string;}

配置文件 tsconfig.json

// 此文件由命令 tsc -init 生成
// 直接运行 tsc 命令就会运用此配置文件
// 选项详解:https://www.tslang.cn/docs/handbook/compiler-options.html
{
  // "include": ["demo15-1.ts"],              // 要编译的指定文件,不配置此项时运行tsc默认编译全部
  // "files": ["demo15-1.ts"],                // 和include类似
  // "exclude": ["demo15-3.ts"],              // 要排除编译的指定文件
  "compilerOptions": {
    // 编译选项
    /* 基本选项 */
    // "incremental": true,                   /* 启用增量编译 */
    "target": "es5" /* 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
    "module": "commonjs" /* 指定模块代码生成: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
    // "lib": [],                             /* 指定要包含在编译中的库文件. */
    // "allowJs": true,                       /* 允许编译javascript文件*/
    // "checkJs": true,                       /* 报告.js文件中的错误。 */
    // "jsx": "preserve",                     /* 指定 JSX代码生成: 'preserve', 'react-native', or 'react'. */
    // "declaration": true,                   /* 生成相应的 .d.ts文件 */
    // "declarationMap": true,                /* 为每个相应的 .d.ts文件生成一个sourcemap (编译后代码对源码的映射)*/
    "sourceMap": true /* 生成源文件与输出文件的映射关系文件(.map)。*/,
    // "outFile": "./",                       /* 将输出文件合并为一个文件. */
    "outDir": "./build" /* 输出的js文件目录。 */,
    "rootDir": "./src" /* ts源文件目录。 */,
    // "composite": true,                     /* 启用 项目编译 */
    // "tsBuildInfoFile": "./",               /* 指定用于存储增量编译信息的文件 */
    // "removeComments": true,                /* 不输出注释到编译结果.(删除所有注释,除了以 /!*开头的版权信息。) */
    // "noEmit": true,                        /* 不发出输出.Do not emit outputs. */
    // "importHelpers": true,                 /* 从“tslib”导入发出助手. Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,               /* 将每个文件作为单独的模块(与“ts.transpileModule”类似) */

    /* 严格的类型检查选项 */
    "strict": true /* 启用所有严格类型检查选项。 打开此选项后,下面这些选项就不需要单独设置*/,
    // "noImplicitAny": true,                 /* 对隐含的“any”类型的表达式和声明引发错误. (为false时允许any不用特意声明)*/
    // "strictNullChecks": true,              /* 启用严格的null检查. (为false时允许赋值为null)*/
    // "strictFunctionTypes": true,           /* 启用严格检查函数类型. */
    // "strictBindCallApply": true,           /* 启用函数上严格的“bind”、“call”和“apply”方法. */
    // "strictPropertyInitialization": true,  /* 启用 严格检查类中的属性初始化. */
    // "noImplicitThis": true,                /* 在隐含的“any”类型的“this”表达式上引发错误。 */
    // "alwaysStrict": true,                  /* 以严格模式解析并为每个源文件发出“use strict”。 */

    /* 附加检查。Additional Checks  */
    // "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": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                       /* List of folders to include type definitions from. */
    // "types": [],                           /* 要包含的类型声明文件名列表 */
    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true /* 启用s emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
    // "allowUmdGlobalAccess": true,          /* Allow accessing UMD globals from modules. */

    /* Source Map Options */
    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,        /* 启用s experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,         /* 启用s experimental support for emitting type metadata for decorators. */
  }
}
上次编辑于: