TS使用归档

TS特有de概念

元组

指定数组每个元素的类型

特点

  • 在初始化时,必须按限定的类型和个数赋值;
  • 可以通过数组的方法突破限制;

示例

let arr: [string, number, any]
arr = ['1', 1, 1] // OK
arr = [1, 1, 1] // Error
arr = ['1', 1, 1, true] // Error
arr.push(true) // OK

接口

只是在 ts编译阶段做类型检查,并不会转译成 JS代码

用接口声明可调用的类型

示例

// 定义函数
interface ISum {
    (x: number, y: number, z?: number): number;
}
let sum: ISum
sum = (a) => {
//     ^ = OK
  return 1
}
sum('1', 2)
//   ^ Argument of type '"1"' is not assignable to parameter of type 'number'.


// 定义类的new调用
interface IConstructor {
  new(arg: string): IConstructor;
}
function Constr (this: any, name: string) {
  this.name = name
}
const instance = new (Constr as any as IConstructor)('111')

以上示例注意:

  • interface定义函数输入、输出的类型作为预置值内置在函数声明中

    • 函数声明时无需二次定义参数类型,函数输出值的类型推断要与interface定义的输出类型一致,否则会报错。
    • interface定义函数没有限制函数声明时传入的参数个数,只有在调用时才会报参数个数错误;
  • 函数声明无法直接其它类型,需要使用双重断言Constr as any as IConstructor

    • ==尽可能不要使用双重断言==,它会影响ts的判断
    // 示例:
    let num: number = 0
    let str: string = 's'
    num = str // Error
    num = str as any as number // OK
    //^ = num === 's' //这里str认为是number类型,赋值成功

联合类型

一个数据声明为联合类型,使用时,若不确定是联合类型中具体的类型时(通过if条件、as断言、in操作符、typeof缩小未知范围),只能访问联合类型共有的方法。

断言

断言是联合类型缩小未知范围时使用,但,断言强制浏览器相信数据是我们指定的类型,实际上是 不安全的。【推荐】使用类型收缩 typeofinstanceof...来缩小未知范围。

当两个类型声明有交集时,才可以使用断言,否则会报错(because neither type sufficiently overlaps with the other.)

如果两个类型声明没有交集,可以使用双重断言强制断言成另一种类型,示例如上:Constr as any as IConstructor

readonly & Readonly泛型

readonly标识符,用于对象属性

Readonly映射类型,接收一个泛型T,用来把泛型T的所有属性标记为只读类型;

示例

type Foo = {
  bar: number;
  bas: number;
}
type ReadFoo = Readonly
/**     ^ = type ReadFoo = {
*               readonly bar: number;
*               readonly bas: number;
*           }
*/

示例

function fn(x: number | string): number {
  return x.length;
}
// Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.

对象结构

示例

// type定义对象结构,不可重载
type TObjectProps = {
  x: number;
  y: number;
}
const obj: TObjectProps = {
  x: 1,
  y: 1
}




// interface定义对象结构
interface IObjectProps {
  x: number;
  y: number;
}
const obj: IObjectProps = {
  x: 1,
  y: 1,
  add() {}
  //^ = 'add' does not exist in type 'IObjectProps'
}

// interface定义重载
interface IObjectProps {
  x: number;
  y: number;
}
const obj: IObjectProps = {
  x: 1,
  y: 1,
  add() {} // OK
}
interface IObjectProps {
  add: () => void;
}




// let & typeof定义对象结构,不可重载
let objectProps: {
  x: number;
  y: number;
}
const obj: typeof objectProps = {
  x: 1,
  y: 1
}

Function

函数类型声明方式有多种,应用场景两种: 固定参数,不固定参数;

示例

固定参数:
// type定义
type Tfn = (a: number, b: number) => number;
let fn1: Tfn
fn1 = function(a, b) {
  return  a + b
}
fn1(1, 2)


// type定义重载
type Tfn = {
  (a: string): string;
  (a: number, b: number): number;
}
let fn1: Tfn
fn1 = function(a: any, b?: any): any {
  if (b) {
    return a + b
  } else {
    return a
  }
}
fn1('1')
//  ^ = let fn1: (a: string) => string (+1 overload)
fn1(1, 2)
//  ^ = let fn1: (a: number, b: number) => number (+1 overload)
fn1(1)  //Error



// interface定义
interface Ifn {
  (x: string): string;
}
let fn1: Ifn
fn1 = function(a) {
  return a
}
fn1('1')

// interface定义重载
interface Ifn {
  (x: string): string;
  (x: number, y: number): number;
}
let fn1: Ifn
fn1 = function(a: any, b?: any): any {
  if (b) {
    return a + b
  } else {
    return a
  }
}
fn1('1')
//  ^ = let fn1: (a: string) => string (+1 overload)
fn1(1, 2)
//  ^ = let fn1: (a: number, b: number) => number (+1 overload)
fn1(1)  //Error



// let & typeof 声明
let fn: {
  (x: string): string;
}
let fn1: typeof fn
fn1 = function(a) {
  return a
}
fn1('1')

// let & typeof声明重载
let fn: {
  (x: string): string;
  (x: number, y: number): number;
}
let fn1: typeof fn
fn1 = function(a: any, b?: any) {
  if (b) {
    return a + b
  } else {
    return a
  }
}
fn1('1')
//  ^ = let fn1: (a: string) => string (+1 overload)
fn1(1, 2)
//  ^ = let fn1: (a: number, b: number) => number (+1 overload)
fn1(1)  //Error



// function声明
function fn(x: string): string {
  return x
}
fn('1')

// function声明重载:fn实现必须紧跟着fn头声明
function fn(x: string): string;
function fn(x: number, y: number): number;
function fn(x: any, y?: any) {
  if (y) {
    return x + y
  } else {
    return x
  }
}
fn('1')
//  ^ = let fn: (a: string) => string (+1 overload)
fn(1, 2)
//  ^ = let fn: (a: number, b: number) => number (+1 overload)
fn(1)  //Error

通过重复定义函数头实现重载,而函数声明输入、输出最好使用any类型(不使用any的话,函数体的操作逻辑使用的必须是联合声明类型共有的成员),调用时会根据参数个数匹配预定义的函数头进行校验。

不固定参数:
// 应用场景:作为回调函数,通过`apply`调用;
interface IFn {
  (...args: any[]): any;
}
function invoke(fn: IFn) {
  fn.apply(null, [...arguments])
}

枚举Enum

定义索引和值的双向映射;
enum Direction {
  UP,
  DOWN,
  LEFT,
  RIGHT
}
console.log(Direction[0]) // 'UP'
console.log(typeof Direction[0]) // 'string'
console.log(Direction['UP']) // 0
console.log(typeof Direction['UP']) // 'number'
console.log(Direction[0] === 'UP') // true

分类

数字枚举
数字枚举默认从0开始;

若有指定的索引,则后续数据索引++

// 场景:Code编码语义化
enum Direction {
  Up,
  Down = 10,
  Left,
  Right
}
console.log(Direction[0]) // 'UP'
console.log(Direction['Up']) // 0
console.log(Direction['Left']) // 11
console.log(Direction[10]) // 'Down'
字符串枚举
// 场景:游戏按键?
enum Direction {
  Up = 'u',
  Down = 'd',
  Left = 'l',
  Right = 'r'
}
console.log(Direction['Up']) // '上'
console.log(Direction['Down']) // '下'
常量枚举
和上述枚举类型 编译结果不同
enum Dir {
  Up,
  Down = 10,
  Left,
  Right
}
const enum Direction {
  Up,
  Down = 10,
  Left,
  Right
}
let directions = [
  Direction.Up,
  Direction.Down,
  Direction.Left,
  Direction.Right,
];

/////编译输出如下:
"use strict";
var Dir;
(function (Dir) {
    Dir[Dir["Up"] = 0] = "Up";
    Dir[Dir["Down"] = 10] = "Down";
    Dir[Dir["Left"] = 11] = "Left";
    Dir[Dir["Right"] = 12] = "Right";
})(Dir || (Dir = {}));
let directions = [
    0 /* Up */,
    10 /* Down */,
    11 /* Left */,
    12 /* Right */,
];

字面量类型

const str: 'name' = 'name' // str只能是'name'字符串,赋值其他字符串或其他类型会报错;
const number: 1 = 1 // number只能是1,赋值其他数值或其他类型会报错;
type Directions = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
let toWhere: Directions = 'LEFT' 

对应场景:

interface IO {
  'y+': number;
  'M+': number;
  'd+': number;
  'h+': number;
  'm+': number;
  's+': number;
  'q+': number;
  'S+': number;
}
type TKeyProps = keyof IO
//  ^ = type TKeyProps = "y+" | "M+" | "d+" | "h+" | "m+" | "s+" | "q+" | "S+"
var o: IO = {
  'y+': this.getFullYear(),
  'M+': this.getMonth() + 1, 
  'd+': this.getDate(),
  'h+': this.getHours(),
  'm+': this.getMinutes(),
  's+': this.getSeconds(),
  'q+': Math.floor((this.getMonth() + 3) / 3),
  'S+': this.getMilliseconds()
}
o['y++'] // OK
let kkk = 's+'
o[kkk] // Error
o[kkk as TKeyProps] // OK

泛型

泛型de目的是在成员之间(至少有两个地方用到了泛型占位)提供有意义的约束

成员:

  • 类的属性
  • 类的方法
  • 函数参数
  • 函数返回值
泛型在定义时,不能明确数据类型,声明一变量 占位,调用时通过传入类型或 ts类型推断来确定具体的数据类型。
  • 相对于any:泛型未丢失数据结构信息;
  • 相对于联合声明:泛型明确具体的类型结构,联合声明并未明确具体类型;
逻辑中只能调用泛型数据的 通用成员属性/方法,否则会报错;

示例

function identity(arg: T): T {
    return arg;
}
// 明确指定T是string类型
let output = identity("myString");  // type of output will be 'string'
// 利用类型推论 -- 即编译器会根据传入的参数自动地帮助确定T的类型
let output = identity("myString");  // type of output will be 'string'

any & never & unknown

  • any

    • 称为top type,任何类型的值都能赋给any类型的变量
    • 又称为bottom type,任何类型(除never外)的子类型
    • 可以理解为没有类型
  • never

    • 称为bottom type,任何类型的子类型,也可以赋值给任何类型
    • 推断场景1:

      无法执行到函数终止点的 函数表达式
      • 场景:总抛出异常的函数表达式的返回值类型;
        // 需要是函数表达式,若是函数声明为assertNever: () => void
        const assertNever = function (x: any) {
        //      ^ = type assertNever = never
          throw new Error("Unexpected object: " + x);
        }
      • 场景:永不结束函数表达式的返回值类型;
        let loop = function () {
        //    ^ = type loop = never
          while (true) {}
        }
    • 推断场景2:被永不为真的类型保护约束的变量类型;
      //  示例:
        type result = 1 & 2 // 结果为never
        //      ^ = type result = never
      // 尤大示例:
        interface Foo {
          type: 'foo'
        }
      
        interface Bar {
          type: 'bar'
        }
      
        type All = Foo | Bar
        function handleValue(val: All) {
          switch (val.type) {
            case 'foo':
              // 这里 val 被收窄为 Foo
              break
            case 'bar':
              // val 在这里是 Bar
              break
            default:
              const exhaustiveCheck = val
              //      ^ = type exhaustiveCheck = never
              break
          }
        }
  • unknown

    • top type:任何类型都是它的subtype
    • 不能将unknown赋值其它类型,unknown类型的数据只能赋值给unknownany类型;
    • 相对于anyts会为unknown提供有效的类型检测;
    • unknown类型数据的属性或方法,需要通过类型断言/类型收缩来缩小未知范围;

any的危害

使用 any做类型声明或者做断言,会丧失原始数据的结构类型信息,即:再也无法知道原始数据是什么结构了,更不会有报错信息, 推荐使用unknown & 类型收缩

索引签名

索引签名用于定义对象/数组de 通配结构

索引签名的名称(如:{ [key: string]: number }key )除了可读性外,没有任何意义,可以任意定义。

其它成员都必须符合索引签名值de结构,所以,索引签名值de结构必须是其它成员属性类型的top type

尽量不要使用字符串索引签名与有效变量混合使用——如果属性名称中有拼写错误,这个错误不会被捕获。【同级的其它属性应该是对索引签名的限制增强

示例

// 1. 对象示例
interface Foo {
  [key: string]: number; // 该通配结构[key: string]即是签名索引。
}
// 1. 数组示例
interface Foo {
  [idx: number]: string;
  length: number;
}
const arr: Foo = ['1', '2', '3', '4']



// 2. 所有明确的成员都必须符合索引签名
  interface Bar {
    [key: string]: number;
    x: number; // OK
    y: string; 
  //^ = Property 'y' of type 'string' is not assignable to string index type 'number'.
  }
// 2. 可以使用交叉类型突破限制
  interface Bar {
    [key: string]: number;
    x: number; 
  }
  type Composition = Bar && {
    y: string;  
  }



// 3. 有效变量和索引签名不要同级定义
interface CSS {
  [selector: string]: string;
  color?: string; 
}
const failsSilently: CSS = {
  colour: 'red' // 'colour' 不会被捕捉到错误
};

TS类型声明

ts报错对应查找:做词典用;

declare声明通过各种途径(