Simple Vue
Challenge
Implement a simpiled version of a Vue-like typing support.
By providing a function name SimpleVue (similar to Vue.extend or defineComponent), it should properly infer the this type inside computed and methods.
In this challenge, we assume that SimpleVue take an Object with data, computed and methods fields as it’s only argument,
-
datais a simple function that returns an object that exposes the contextthis, but you won’t be accessible to other computed values or methods. -
computedis an Object of functions that take the context asthis, doing some calculation and returns the result. The computed results should be exposed to the context as the plain return values instead of functions. -
methodsis an Object of functions that take the context asthisas well. Methods can access the fields exposed bydata,computedas well as othermethods. The different betweencomputedis thatmethodsexposed as functions as-is.
The type of SimpleVue’s return value can be arbitrary.
const instance = SimpleVue({
data() {
return {
firstname: "Type",
lastname: "Challenges",
amount: 10,
};
},
computed: {
fullname() {
return this.firstname + " " + this.lastname;
},
},
methods: {
hi() {
alert(this.fullname.toLowerCase());
},
},
});
Solution
Damit man die Lösung dieses Problems nachvollziehen kann, sollte man sich mit dem ThisType auskennen.
Die Funktionsdeklaration enthält drei generische Typen D (Data), M (Methods) und C (Computed).
declare function SimpleVue<D, M, C>(options: {}): any;
Die Typdeklaration von data ist initial ein leeres Objekt, entsprechend geben wir ein leeres Objekt oder D zurück. This ist dabei ein leeres Objekt, da wir in data nicht auf andere Eigenschaften wie methods zugreifen dürfen.
data: (this: {}) => D;
Da Computed-Werte in Vue für den Kontext als Werte, und nicht als Funktion zur Verfügung stehen, müssen wir mittels infer den Rückgabewert der Funktion ermitteln und einen Mapped Type erstellen, der statt den Funktionen nur den Wert der Rückgabe enthält.
// Before: e.g fullname: () => boolean; After: fullname: boolean
type ComputedValues<C> = {
[Key in keyof C]: C[Key] extends (...args: any[]) => infer Return
? Return
: never;
};
// Zwischenergebnis:
declare function SimpleVue<D, M, C>(options: {
data: (this: {}) => D;
// Da man in Computed auf data, sowie andere Computed-Werte zugreifen kann, setzen wir den `ThisType` auf `D`.
computed: ThisType<D> & C;
}): any;
Nun können wir abschließend noch methods typisieren, hier kann man auf data, methods sowie computed zugreifen:
methods: M & ThisType<D & M & ComputedValues<C>>;
Somit ergibt sich als finaler Funktionstyp folgende Deklaration:
type ComputedValues<C> = {
[Key in keyof C]: C[Key] extends (...args: any[]) => infer Return
? Return
: never;
};
declare function SimpleVue<D, M, C>(options: {
data: (this: {}) => D;
computed: ThisType<D & C> & C;
methods: M & ThisType<D & M & ComputedValues<C>>;
}): any;