美文网首页
TS使用归档

TS使用归档

作者: 说叁两事 | 来源:发表于2021-01-14 15:43 被阅读0次

    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<Foo>
    /**     ^ = 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<T>(arg: T): T {
        return arg;
    }
    // 明确指定T是string类型
    let output = identity<string>("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声明通过各种途径(<script>导入、new webpack.ProvidePlugin({//...}))引入全局命名空间的变量/模块

    函数声明中显式使用this

    // 场景示例:
    function Person() {
      var vm = this;
      //         ^ = this具有隐式类型any;
    }
    
    
    
    // 解决方案:
    function Person(this: void) {
      var vm = this;
    }
    

    this作为函数的第一参数,由于ts只是类型检查,编译成JS时不会将this参数输出。

    全局库声明

    // 场景示例:
    $('div')[]
    //^ = not find name '$'
    
    
    
    
    // 解决方案:追加zepto.d.ts声明
    interface ZeptoStatic {
      //...
    }
    interface ZeptoCollection {
      // ...
    }
    declare var Zepto: (fn: ($: ZeptoStatic) => void) => void;
    declare var $: ZeptoStatic;
    declare function $(selector?: string, context?: any): ZeptoCollection;
    

    JS内置对象追加属性声明

    // 场景示例:
    Date.prototype.format = function() {
      //...
    }
    
    const arg = 2423423413;
    new Date().format(arg)
    //          ^ = format 不在Date上 
    
    
    
    
    
    // 解决方案:追加声明*.d.ts声明
    declare global {
      interface Date {
        Format(arg: string): string;
      }
    }
    

    图片...静态资源声明

    // 场景示例:
    import Logo from './assets/logo.png'
    //      ^ = not find Module
    
    
    
    
    // 解决方案:追加声明*.d.ts声明
    declare module '*.svg'
    declare module '*.png'
    declare module '*.jpg'
    declare module '*.jpeg'
    declare module '*.gif'
    declare module '*.bmp'
    declare module '*.tiff'
    

    vue3配置全局成员

    // 场景示例:
    // main.ts
    import { createApp } from 'vue'
    import axios from 'axios'
    import qs from 'qs'
    import App from './App.vue'
    
    const vueInstance = createApp(App)
    vueInstance.config.globalProperties.$http = axios
    vueInstance.config.globalProperties.$qs = qs
    
    // *.vue
    ...
    this.$http.post(...).then(() => {})
    //    ^ = Property '$http' does not exist on type 'ComponentPublicInstance<>'...
    ...
    
    
    
    
    // 解决方案:追加声明*.d.ts
    import Axios from "axios";
    import qs from 'qs'
    import Store from "../store";
    
    declare module '@vue/runtime-core' {
      interface ComponentCustomProperties {
        $http: Axios;
        $store: Store;
        $qs: qs;
      }
    }
    

    第三方ES Module:export default声明

    // 场景示例:
    // utils.ts
    export default (function() {
      const utils = {
          
      }
      
      return utils
    })() 
    // *.ts
    utils.default.isString(123)
    //    ^ = Property 'default' does not exist on type 'typeof utils'
    
    
    
    
    
    
    // 解决方案:追加声明*.d.ts
    declare namespace utils {
      const UtilsProps: {
        isString: (arg: unknown) => boolean;
        isArray: (arg: unknown)=> boolean;
        isObject: (arg: unknown)=> boolean;
        isDate: (arg: unknown)=> boolean;
        app: any;
      }
      export default UtilsProps;
    }
    

    第三方ES Module:export声明

    // *.d.ts
    declare namespace utils {
      export function isString (arg: unknown): boolean;
      export var app: any;
    }
    

    第三方CommonJS模块声明

    // 模块类库  module-lib.ts
    function moduleLib(options) {
       console.log(options);
    }
    
    const version = "1.0.0";
    function doSomething() {
       console.log('moduleLib do something');
    }
    moduleLib.version = version;
    moduleLib.doSomething = doSomething;
    module.exports = moduleLib;
    
    
    
    
    
    // *.d.ts
    declare function moduleLib(options: Options): void;
    interface Options {
       [key: string]: any,
    }
    declare namespace moduleLib{
       const version: string;
       function doSomething(): void;
    }
    export = moduleLib;
    

    第三方'UMD'声明

    // UMD库  umd-lib.js
    (function (root, factory) {
       if (typeof define === "function" && define.amd) {
          define(factory);
       } else if(typeof module === "object" && module.exports) {
          module.exports = factory();
       } else {
          root.umdLib = factory();
       }
    })(this, function () {
       return {
          version: "1.0.2",
          doSomething() {
             console.log('umdLib do something');
          }
       }
    });
    
    
    
    
    
    
    // *.d.ts文件
    declare namespace umdLib {
       const version: string;
       function doSomething(): void;
    }
    export as namespace umdLib // 专门为umd库准备的语句,不可缺少
    export = umdLib // commonjs导出
    

    参考书

    相关文章

      网友评论

          本文标题:TS使用归档

          本文链接:https://www.haomeiwen.com/subject/sxuvaktx.html