As an illustration, let's consider a basic linked list class in TypeScript version 3.7.5. A LinkedList<T>
is composed of a series of ListNode<T>
, where the type variable T
remains consistent between them. In this scenario, a private static field is utilized within LinkedList
to conceal ListNode
, which is considered extraneous from an implementation standpoint.
class LinkedList<T> {
private head: ??? = null;
private tail: ??? = null;
private static ListNode = class ListNode<T> {
constructor(
public val: T | null,
public next: ListNode<T> | null) {}
};
append(item: T): this {
if (!this.tail) {
this.tail = {val: item, next: null};
this.head = this.tail;
} else {
this.tail.next = {val: item, next: null};
this.tail = this.tail.next;
}
return this;
};
remove(): T {
if (!this.head || this.head.val === null) {
throw Error();
} else {
const t = this.head.val;
this.head = this.head.next;
return t;
}
}
}
What should be used as the placeholder type ???
in the code above? It's neither List.ListNode
nor List.ListNode<T>
. This isn't recognized as valid TypeScript (at least not in version 3.7.5). Another option is not
InstanceType<typeof List.ListNode>
. This may be a valid type, but it disregards the generic parameter T
, thus failing to enforce that both the enclosing and nested classes are parameterized by the same type.
Now, we make adjustments to the class by introducing a dummy head
and relying on type inference for further guidance:
class LinkedList<T> {
private head = LinkedList.makeNode<T>();
private tail = this.head.next;
private static makeNode<T>() {
return new this.ListNode<T>(null, null);
}
private static ListNode = class ListNode<T> {
constructor(
public val: T | null,
public next: ListNode<T> | null) {}
};
append(item: T): this {
if (!this.tail) {
this.head.next = {val: item, next: null};
this.tail = this.head.next;
} else {
this.tail.next = {val: item, next: null};
this.tail = this.tail.next;
}
return this;
};
remove(): T {
if (!this.head.next || this.head.next.val === null) {
throw Error();
} else {
const t = this.head.next.val;
this.head.next = this.head.next.next;
return t;
}
}
}
With this modified code, TypeScript can verify that instances of T
are indeed returned by the remove()
method. Upon hover-over, Visual Studio Code indicates that the type of head
is ListNode<T>
. How can this type be explicitly expressed?