一般情况下class内部的函数是没有tree-shaking的

从一个最简单的Person类开始

export class Person {
  name: string = 'yihan'
  age: number = 25

  run() {
    console.log('person run')
  }

  shout() {
    alert('person shout')
  }
}

new Person().run()    //log: person run

当我们用rollup打包,尽管我们没有用到shout(),打包之后的代码里还是包括了shout()

rollup bundle:

class Person {
    constructor() {
        this.name = 'yihan';
        this.age = 25;
    }
    run() {
        console.log('person run');
    }
    shout() {
        alert('person shout');
    }
}
new Person().run()

这基本是必然的,因为类本身就是一个函数,tree-shaking到类这里就停下了

所以为了tree-shaking,第一步我们尝试把函数从类里剥离出来
于是有了下面这样的结果

export class Person {
  name: string = 'yihan'
  age: number = 25
}

function run() {
  console.log('person run')
}

function shout() {
  alert('person shout')
}

而且我们要通过类实例化的对象去调用这两个函数,所以改写下Person类

export class Person {
  name: string = 'yihan'
  age: number = 25
  do(func: Function, ...args: unknown[]) {
    return func.call(this, ...args)
  }
}

类内部新建一个do()函数来调用外部函数

export class Person {
  name: string = 'yihan'
  age: number = 25
  do(func: Function, ...args: unknown[]) {
    return func.call(this, ...args)
  }
}

function run() {
  console.log('person run')
}

function shout() {
  alert('person shout')
}

new Person().do(run)  //log: person run

rollup打包下看看

rollup bundle:

class Person {
    constructor() {
        this.name = 'yihan';
        this.age = 25;
    }
    do(func, ...args) {
        return func.call(this, ...args);
    }
}
function run() {
    console.log('person run');
}
new Person().do(run)

确实消去了没用到的shout(),效果挺美的,有一说一,确实

但是

如果是javascript确实到这里就完事了,但我们是typescript
单纯这么玩,函数的类型推导就没用了
如果run()函数有参数,无法约束do(run())时候的传参
如果run()函数有返回值,也无法确定类型,this也无法调用到实例的属性,我们又开始写anyscript了

所以我们需要增强class里面的do函数,让do()有自动推断传进来函数的能力

  1. 提取函数的参数类型和返回值类型
    这里就结合 ts 的 泛型infer 关键字
    关于 infer TsDoc Advanced Types 说实话ts中文文档没有这个
    infer 关键字根据文档简单概括下:就是在条件类型中,可以用infer声明一个待推断的类型变量

    Within the`extends`clause of a conditional type, it is now possible to have`infer`declarations that introduce a type variable to be inferred   

    文档里已经有例子告诉我们怎么提取函数的返回值类型

    For example, the following extracts the return type of a function type:
    type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

    我们依葫芦画瓢写个泛型类型去提取参数类型

    export type ParamsOf<T extends Function> = T extends (...args: infer P) => unknown
      ? P
      : never

    再稍微改造下文档里的类型,更严格约束下类型(实在不想看到 any这个玩意)

    export type ReturnOf<T extends Function> = T extends (...args: ParamsOf<T>) => infer R
      ? R
      : never

    然后重写Person类增强我们的 do() 函数,并且试试复杂函数

    export class Person {
      name: string = 'yihan'
      age: number = 25
      do<T extends Function>(func: T, ...args: ParamsOf<T>): ReturnOf<T> {
        return func.call(this, ...args)
      } 
    }
    
    function run() {
      console.log('person run')
    }
    
    function makeCall(tele: number) {
      console.log(`has called ${tele}`)
      return tele + 1
    }
    
    function shout() {
      alert('person shout')
    }
    
    let res = new Person().do(makeCall, 110)     //log: has called 110
    console.log(res)                             //log: 111

    看看自动的类型推导:
    typescript.png
    不错

  2. this怎么绑定类型
    我们在do()里面已经为传进来的函数绑定了this作为外部函数的this
    但是外部函数并不知道传进来的this的类型
    其实typescript已经帮我们解决了,在所有的函数的参数列表的开头可以传进一个this参数,这个this参数不起任何作用,也不占用函数调用时的参数位置,只是帮我们约束下this的类型
    TsDoc Functions
    所以我们稍微改造下外部函数makeCall()

    function makeCall(this: Person, tele: number) {
      console.log(`has called ${tele}`)       //log: has called 110
      console.log(this.name)                  //log: yihan
      return tele + 1
    }

    挺好

  3. 通用性
    总不能每写一个类都写这么多吧
    OK,我们把需要tree-shaking的类提取成一个抽象类不就完事了
    所以最后的代码就会像这样

    treeShaked.ts

    export abstract class TreeShaked {
      do<T extends Function>(func: T, ...args: ParamsOf<T>): ReturnOf<T> {
        return func.call(this, ...args)
      }
    }
    
    type ParamsOf<T extends Function> = T extends (...args: infer P) => unknown
      ? P
      : never
    
    type ReturnOf<T extends Function> = T extends (...args: ParamsOf<T>) => infer R
      ? R
      : never
    import { treeShaked } from 'treeShaked.ts'
    
    export class Person extends TreeShaked {
      name: string = 'yihan'
      age: number = 25
    }
    
    function run() {
      console.log('person run')
    }
    
    function makeCall(this: Person, tele: number) {
      console.log(`has called ${tele}`)
      console.log(this.name)
      return tele + 1
    }
    
    function shout() {
      alert('person shout')
    }
    
    let res = new Person().do(makeCall, 110)
    console.log(res)

    看上去也不是特别糟糕

Rollup打包下看看
test.png
确实起到了tree-shaking的作用


tarnishablec
2 声望1 粉丝