typescript如何在Vue提供this类型推断

Vue中option的类型推断

若是你们有用ts写代码,会发现当咱们写组件的option(选项)时,可以很好的提供类型推断,固然前提是你要使用Vue.extend()方法。javascript

具体的使用你们能够参考我写的这篇博客,如何在vue中不借助vue-class-decorator实现ts类型推断。vue

vue2中使用tsjava

那vue的类型是如何实如今参数中为this提供类型推断呢?segmentfault

如下这段代码在javascript可以很好的工做,也很容易理解。可是当咱们切换到ts作静态类型检查时,this并不能很好的工做,那咱们如何让this可以提供类型推断呢?函数

export default {
    data: {
        first_name: "Anthony",
        last_name: "Fu",
    },
    computed: {
        full_name() {
            return this.first_name + " " + this.last_name;
        },
    },
    methods: {
        hi() {
            alert(this.full_name);
        },
    },
};

this提供类型

为了能让this显示的推断类型,咱们能够采起传参的方式this

interface Context {
    $injected: string
}
function bar(this: Context, a: number) {
    this.$injected // 这样是能工做的
}

可是,若是咱们传入Record参数类型(索引对象),这样就会有问题,它(ts)并不能很好提供类型校验了code

type Methods = Record<string, (this: Context, ...args:any[]) => any>
const methods: Methods = {
    bar(a: number) {
        this.$injected // ok
    }
}
methods.bar('foo', 'bar') // 没有提示错误,由于参数类型已经变为 `any[]`

并且也不能总是让用户,提供参数类型吧!这种体验是很是不友好的,因此为了实现类型校验,咱们须要寻找另外一种方法了。对象

ThisType

在了解了vue的代码以后,发现了ts一个颇有用的内置类型 -ThisType索引

ThisType定义:经过 ThisType咱们能够在对象字面量中键入 this,并提供经过上下文类型控制 this类型的便捷方式。它只有在 --noImplicitThis的选项下才有效

ThisType能够影响全部的嵌套函数,那咱们能够这样写了接口

type Methods = {
    double: (a: number) => number
    deep: {
         nested: {
            half: (a: number) => number
         }
    }
}

const methods: Methods & ThisType<Methods & Context> = {
    double(a: number) {
        this.$injected // ok
        return a * 2
    },
    deep: {
        nested: {
            half(a: number) {
                this.$injected // ok
                return a / 2
            }
        }
    }
}

methods.double(2) // ok
methods.double('foo') // error
methods.deep.nested.half(4) // ok

能够看到this的类型推断已经生效了,可是有个缺点仍是须要用户去定义方法的接口,那咱们能不能自动推断类型呢?

实现define

能够的,经过函数来自动推断类型。

type Options<T> = {
 methods?: T 
} & ThisType<T & Context>

function define<T>(options: Options<T>) {
    return options
}

define({
    methods: {
        foo() {
            this.$injected // ok
        },
    },
})

方法已经能自动推断了,那么接下来,咱们能够接着实现computeddata的类型推断

整个完整的demo以下:

/* ---- Type ---- */

export type ExtractComputedReturns<T extends any> = {
[key in keyof T]: T[key] extends (...args: any[]) => infer TReturn
 ? TReturn
 : never
}

type Options<D = {}, C = {}, M = {}> = {
    data: () => D
    computed: C
    methods: M
    mounted: () => void
    // and other options
} 

& ThisType<D & M & ExtractComputedReturns<C>> // merge them together

function define<D, C, M>(options: Options<D, C, M>) {}

/* ---- Usage ---- */
define({
    data() {
        return {
            first_name: "Anthony",
            last_name: "Fu",
        }
    },
    computed: {
        fullname() {
           return this.first_name + " " + this.last_name
        },
    },
    methods: {
        notify(msg: string) {
            alert(msg)
        }
    },
    mounted() {
        this.notify(this.fullname)
    },
})

其实define的原理就是Vue.extend能推断this(上下文类型)的原理了