127 lines
2.8 KiB
Plaintext

A fluent interface designates an object-oriented API relying on method chaining to improve code readability.
In such interfaces, methods return the object `this` to allow the caller to chain multiple method invocations.
[source,javascript]
----
class RichText {
constructor(private readonly text: string) {}
bold(): RichText {
// [...]
return this;
}
italic(): RichText {
// [...]
return this;
}
}
const richText = new RichText('Hello, World!');
// Chaining methods bold() and italic().
console.log(richText.bold().italic());
----
To better support fluent interfaces when used with a hierarchy of classes,
TypeScript provides a special type `this` that refers dynamically to the type of the current class.
Methods returning `this` should thus use the corresponding special type `this`
instead of the class name in their signatures.
== Why is this an issue?
When a method return type is the declaring class name in a hierarchy of classes,
it is impossible to chain methods defined in the superclass with methods defined in subclasses.
[source,javascript]
----
enum Color {
RED, BLUE, GREEN
}
class Shape {
// The return type is the class name.
move(x: number, y: number): Shape {
// [...]
return this;
}
}
class Polygon extends Shape {
fill(color: Color): Polygon {
// [...]
return this;
}
}
const polygon = new Polygon();
polygon.move(1.0, 2.0).fill(Color.RED);
// ^^^^
// Property 'fill' does not exist on type 'Shape'.
----
== How to fix it
When a method declaration uses the special type `this` instead of the class name for its return type,
TypeScript will use the type of the object `this` instead of the method declaring class
and will accept to invoke methods defined in the object class:
[source,javascript]
----
enum Color {
RED, BLUE, GREEN
}
class Shape {
// The return type is now "this"
move(x: number, y: number): this {
// [...]
return this;
}
}
class Polygon extends Shape {
fill(color: Color): this {
// [...]
return this;
}
}
const polygon = new Polygon();
polygon.move(1.0, 2.0).fill(Color.RED); // No compilation error
----
=== Code examples
==== Noncompliant code example
[source,javascript,diff-id=1,diff-type=noncompliant]
----
class A {
foo(): A { // Noncompliant
return this;
}
bar = (): A => { // Noncompliant
return this;
};
}
----
==== Compliant solution
[source,javascript,diff-id=1,diff-type=compliant]
----
class A {
foo(): this {
return this;
}
bar = (): this => {
return this;
};
}
----
== Resources
=== Documentation
* TypeScript Documentation - https://www.typescriptlang.org/docs/handbook/2/classes.html#this-types[``++this++`` Types]
* Wikipedia - https://en.wikipedia.org/wiki/Fluent_interface[Fluent interface]