Typescript 的优点

  • 程序更容易理解;例如使用函数和对象时有提示的入参和属性等
  • 开发效率更高;跳转定义很方便,代码补全等
  • 更少的低级错误;结合TS的一些校验规则,基本排除了一些变量名写错等低级错误
  • 兼容性好;可以最快速的使用 ECMA 提供的最新的特性,而不用考虑兼容问题

原始类型

// Boolean
const isShow: boolean = false

// Null   在 MDN 中的 javascript 数据类型中,null是一种单独的类型,不属于原始类型 typeof null === 'object'
const n: null = null

// Undefined
const u: undefined = undefined

// Number
const age: number = 18

// BigInt  创建 BigInt 类型的值也非常简单,只需要在数字后面加上 n 即可
let time: bigint = 123n
let time: bigint = BigInt(123)
BigInt(123) === 123n     // true

// String
const str: string = 'name'

// Symbol 表示独一无二的值
const s1: symbol = Symbol('Hello')

// undefined 和 null 是所有类型的子类型
const num: number = null

其他类型

Any

const notSure: any = 4

Unknown

const notSure: unknown = 4

any 和 unknown 的区别: any 能赋值给任意类型的变量,unknown 类型的值不能赋值给 any 和 unknown 以外的类型变量。

Array

const arr: number[] = [1, 3, 6]

Tuple 元组

const user : [string, number] = ['Joke', 18]
user.push(24) // 只能push元组已定义的类型

Interface

对对象、函数、类的形状进行描述(duck typing)

interface Person {
  name: string,
  age: number,
  readonly sex: number, // 只读属性
  id ? : string  // ?表示可有可无
}
const Job : Person = {
  name: 'Job',
  age: 26,
  sex: 1
}

Function

function add(x : number, y : number, z?: number) : number {
  return x + y
}
const res = add(1, 2)
const add2 : (x : number, y : number) => number = add // 函数赋值
let add3: (x: number, y: number, z: number) => number = add // add2 和 add3 的类型都是 add 的类型的子集,就像 null 是所有类型的子集
// 使用 Interface 描述函数
interface Sum {
  (x : number, y : number) : number
}
let add3 : Sum = add

联合类型 union-types

let aa : number | string = 23
aa = 'aa'
aa.length // 报错 因为Number没有length方法

类型断言 使用联合类型时,若断言了类型,即可使用其 API

告诉编译器: “相信我,我知道自己在干什么”

function getLength (input: string | number): number {
  const str = input as string
  if (str.length) {
    return str.length
  }
  const num = input as number
  return num.toString().length
}

function getLength (input: string | number): number {
  if (typeof input === 'string') { // 也是断言的一种
    return input.length
  } else {
    return input.toString().length
  }
}

function isFish(animal: Cat | Fish): boolean {
  // 将 animal 断言成 Fish 类才能访问 swim 方法
  if (typeof (animal as Fish).swim === 'function') {
    return true
  }
  return false
}

// 尖括号断言
// let someValue: any = 'some str';
// let strLength: number = (<string>someValue).length

// 谓词断言
function unknownToString(data: unknown): data is string {
  return typeof data === 'string'
}

const aa: unknown = [123,33]
if(unknownToString(aa)){
  const bb = aa // aa => string
}

注意:断言只能够‘欺骗’编译器不报错,无法避免运行时的报错,所以滥用断言很可能导致运行时错误

理想情况下,TypeScript 的类型系统运转良好,每个值的类型都具体而精确。 当我们引用一个在此类型上不存在的属性或方法时,就会报错,但有的时候,我们非常确定这段代码不会出错,比如下面这个例子:

window.foo = 1;
// index.ts:1:8 - error TS2339: Property 'foo' does not exist on type 'Window & typeof globalThis'.

此时我们可以使用 as any 临时将 window 断言为 any 类型:(window as any).foo = 1; 有时候我们也需要将 any 断言为一个具体的类型,例如:

function getCacheData(key: string): any {
    return (window as any).cache[key];
}
interface Cat {
    name: string;
    run(): void;
}
const tom = getCacheData('tom') as Cat;
tom.run();

我们调用完 getCacheData 之后,立即将它断言为 Cat 类型。这样的话明确了 tom 的类型,后续对 tom 的访问时就有了代码补全,提高了代码的可维护性。

总结:

  • 联合类型可以被断言为其中一个类型
  • 父类可以被断言为子类
  • 任何类型都可以被断言为 any
  • any 可以被断言为任何类型

Class 类

class Person {
  constructor () {}
  add () {} // 默认public,本类不可访问不可修改,子类不能访问不能修改,实例可访问可修改
  static run () {} // 静态属性或方法,不需要实例化,可直接通过类调用 Person.run() ,但是不能通过 this. 的方式访问修改,子类类名可访问可修改(子类也是不实例化的时候访问修改),实例不能访问不能修改
  private sum () {} // 私有属性或方法,子类中不能访问不能修改,实例不能访问和修改(仅编译报错,实际执行可访问可修改)
  protected aaa () {} // 受保护的属性和方法,子类中可访问可修改,实例不能访问和修改(仅编译报错,实际执行可访问可修改)
  readonly ccc // 只读属性,只能作用于属性,本类实例可访问不能修改,子类中可访问不能修改,实例可访问不能修改
}

// 除 static 外,其他类型的子类指的都是子类中通过 this. 的方式访问和修改

implements

抽象类的属性和方法 —— 可以继承多个类,interface 只定义方法名,类中必须要实现 implements 的方法

interface Radio {
  switchRadio (bool: boolean): void
}
interface Battery {
  checkStatus (): void
}
class Car implements Radio {
  switchRadio () {}
}
class Phone implements Radio, Battery {
  switchRadio () {}
  checkStatus () {}
}

Enum 枚举

enum Week {
  first = 1,
  tues = 2,
  wes = 3
}
console.log(Week.tues)
console.log(Week[2])
enum Status { // 默认按索引值从0开始递增 0,1,2
  accept,
  follow,
  saved
}

type alias 类型别名

type PlusType = (x: number, y: number) => number
let add: PlusType
type Position<T> = {x: T, y: T}
const p1 : Position<number> = {
  x: 3,
  y: 5
}
const p2: Position<string> = {
  x: 'xxx',
  y: 'yyy'
}
// 1. 类型别名无法被 extends 和 implements,所以类型需要拓展时,需使用接口;
// 2. 当无法通过接口,并且需要使用联合类型或者元组类型时,用类型别名;

字面量

type Direct = 'up' | 'down' | 'right' | 'left'
const move : Direct = 'down'

Generics 泛型

定义时不指定类型,使用时再判断类型

function echo<T>(aa: T): T{
  return aa
}
const str : string = 'asd'
const res = echo(str) // res的类型由传入的参数的类型决定
// 约束泛型
interface IWithLength {
  length: number
}
function echoWithLength<T extends IWithLength>(aaa: T): T {
  console.log(aaa.length)
  return aaa
}

使用泛型时,我们应该遵循两个标准: 1.当函数、接口或类处理各种数据类型时 2.当函数、接口或类在多个位置使用该数据类型时

// 泛型类
class Person<T>{
    private _value: T;
    constructor(val: T) {
        this._value = val;
    }
}
let p = new Person<number>(12)

// 泛型函数
function fn<T>(arg: T): T {
    return arg;
}
fn<number>(12); // 编辑器可以推断传入的参数,可以省略 <number> 

// 定义接口
interface Identities<V, M> {
    value: V,
    message: M
}

let identities: Identities<number, string> = {
    value: 123,
    message: 'msg'
}

// 定义箭头函数
const foo = <T>(x: T): T => x

如何在 vue 文件中定义组件 ref

const xxxRef = ref<InstanceType<typeof ComponentName> | null>(null)

TS 扩展 window

// global.d.ts
declare global {
  interface Window {
    xxx: string
  }
}
export {}

之后检查 global.d.ts 是否在 tsconfig.jsoninclude