Typed 'this' in object literal methods by ahejlsberg · Pull Request #14141 · microsoft/TypeScript (original) (raw)

With this PR we strongly type this in methods of object literals and provide a facility for controlling the type of this through contextual typing. The new behavior is only enabled in --noImplicitThis mode.

The type of the expression this in a method of an object literal is now determined as follows:

Some examples:

// Compile with --noImplicitThis

type Point = { x: number; y: number; moveBy(dx: number, dy: number): void; }

let p: Point = { x: 10, y: 20, moveBy(dx, dy) { this.x += dx; // this has type Point this.y += dy; // this has type Point } }

let foo = { x: "hello", f(n: number) { this; // { x: string, f(n: number): void } }, }

let bar = { x: "hello", f(this: { message: string }) { this; // { message: string } }, }

In a similar manner, when --noImplicitThis is enabled and a function expression is assigned to a target of the form obj.xxx or obj[xxx], the contextual type for this in the function expression is obj:

// Compile with --noImplicitThis

obj.f = function(n) { return this.x - n; // 'this' has same type as 'obj' }

obj['f'] = function(n) { return this.x - n; // 'this' has same type as 'obj' }

In cases where an API produces a this value by transforming its arguments, a new ThisType<T> marker interface can be used to contextually indicate the transformed type. Specifically, when the contextual type for an object literal is ThisType<T> or an intersection including ThisType<T>, the type of this within methods of the object literal is T.

// Compile with --noImplicitThis

type ObjectDescriptor<D, M> = { data?: D; methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M }

function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M { let data: object = desc.data || {}; let methods: object = desc.methods || {}; return { ...data, ...methods } as D & M; }

let obj = makeObject({ data: { x: 0, y: 0 }, methods: { moveBy(dx: number, dy: number) { this.x += dx; // Strongly typed this this.y += dy; // Strongly typed this } } });

obj.x = 10; obj.y = 20; obj.moveBy(5, 5);

In the example above, the methods object in the argument to makeObject has a contextual type that includes ThisType<D & M> and therefore the type of this in methods within the methods object is { x: number, y: number } & { moveBy(dx: number, dy: number): number }. Notice how the type of the methods property simultaneously is an inference target and a source for the this type in methods.

The ThisType<T> marker interface is simply an empty interface declared in lib.d.ts. Beyond being recognized in the contextual type of an object literal, the interface acts like any empty interface.

Patterns similar to the above are used in several frameworks, including for example Vue and Ember. Using ThisType<T> we can now more accurately describe those frameworks.

Supercedes #8382. We revoked that PR because it always made the type of this in object literal methods be the type of the object literal. We now make that the default behavior, but allow the default to be overridden using a ThisType<T> contextual type.