一般情况下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()有自动推断传进来函数的能力
-
提取函数的参数类型和返回值类型
这里就结合 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
看看自动的类型推导:
不错 -
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 }
挺好
-
通用性
总不能每写一个类都写这么多吧
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打包下看看
确实起到了tree-shaking的作用
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。