본문으로 건너뛰기

this 타입

클래스에서 this라는 특별한 타입은 현재 클래스의 타입을 동적으로 참조합니다. 이것이 어떻게 유용한지 알아보겠습니다.

ts
class Box {
contents: string = "";
set(value: string) {
(method) Box.set(value: string): this
this.contents = value;
return this;
}
}
ts
class Box {
contents: string = "";
set(value: string) {
(method) Box.set(value: string): this
this.contents = value;
return this;
}
}

여기서 타입스크립트는 set의 반환 타입을 Box가 아닌 this로 추론했습니다. 이제 Box의 하위 클래스를 만들어 보겠습니다.

ts
class ClearableBox extends Box {
clear() {
this.contents = "";
}
}
 
const a = new ClearableBox();
const b = a.set("hello");
const b: ClearableBox
ts
class ClearableBox extends Box {
clear() {
this.contents = "";
}
}
 
const a = new ClearableBox();
const b = a.set("hello");
const b: ClearableBox

매개변수 타입 주석에서도 this를 사용할 수 있습니다.

ts
class Box {
content: string = "";
sameAs(other: this) {
return other.content === this.content;
}
}
ts
class Box {
content: string = "";
sameAs(other: this) {
return other.content === this.content;
}
}

이는 other: Box를 작성하는 것과 다릅니다. 파생 클래스의 sameAs 메서드는 이제 동일한 파생 클래스의 다른 인스턴스만 허용합니다.

ts
class Box {
content: string = "";
sameAs(other: this) {
return other.content === this.content;
}
}
 
class DerivedBox extends Box {
otherContent: string = "?";
}
 
const base = new Box();
const derived = new DerivedBox();
derived.sameAs(base);
Argument of type 'Box' is not assignable to parameter of type 'DerivedBox'. Property 'otherContent' is missing in type 'Box' but required in type 'DerivedBox'.2345Argument of type 'Box' is not assignable to parameter of type 'DerivedBox'. Property 'otherContent' is missing in type 'Box' but required in type 'DerivedBox'.
ts
class Box {
content: string = "";
sameAs(other: this) {
return other.content === this.content;
}
}
 
class DerivedBox extends Box {
otherContent: string = "?";
}
 
const base = new Box();
const derived = new DerivedBox();
derived.sameAs(base);
Argument of type 'Box' is not assignable to parameter of type 'DerivedBox'. Property 'otherContent' is missing in type 'Box' but required in type 'DerivedBox'.2345Argument of type 'Box' is not assignable to parameter of type 'DerivedBox'. Property 'otherContent' is missing in type 'Box' but required in type 'DerivedBox'.

this 기반 타입 가드

클래스와 인터페이스 메서드의 반환 위치에서 this is Type을 사용할 수 있습니다. if문 같은 타입 좁히기와 혼합되면 대상 객체의 타입이 지정된 Type으로 좁혀집니다.

ts
class FileSystemObject {
isFile(): this is FileRep {
return this instanceof FileRep;
}
isDirectory(): this is Directory {
return this instanceof Directory;
}
isNetworked(): this is Networked & this {
return this.networked;
}
constructor(public path: string, private networked: boolean) {}
}
 
class FileRep extends FileSystemObject {
constructor(path: string, public content: string) {
super(path, false);
}
}
 
class Directory extends FileSystemObject {
children: FileSystemObject[];
}
 
interface Networked {
host: string;
}
 
const fso: FileSystemObject = new FileRep("foo/bar.txt", "foo");
 
if (fso.isFile()) {
fso.content;
const fso: FileRep
} else if (fso.isDirectory()) {
fso.children;
const fso: Directory
} else if (fso.isNetworked()) {
fso.host;
const fso: Networked & FileSystemObject
}
ts
class FileSystemObject {
isFile(): this is FileRep {
return this instanceof FileRep;
}
isDirectory(): this is Directory {
return this instanceof Directory;
}
isNetworked(): this is Networked & this {
return this.networked;
}
constructor(public path: string, private networked: boolean) {}
}
 
class FileRep extends FileSystemObject {
constructor(path: string, public content: string) {
super(path, false);
}
}
 
class Directory extends FileSystemObject {
children: FileSystemObject[];
}
 
interface Networked {
host: string;
}
 
const fso: FileSystemObject = new FileRep("foo/bar.txt", "foo");
 
if (fso.isFile()) {
fso.content;
const fso: FileRep
} else if (fso.isDirectory()) {
fso.children;
const fso: Directory
} else if (fso.isNetworked()) {
fso.host;
const fso: Networked & FileSystemObject
}

this 기반 타입 가드의 일반적인 사용 사례는 특정 필드의 느린 유효성 검사를 허용하는 것입니다.

예를 들어 다음 사례는 hasValue가 참으로 확인되었을 때 Box 안의 값에서 undefined가 제거됩니다.

ts
class Box<T> {
value?: T;
 
hasValue(): this is { value: T } {
return this.value !== undefined;
}
}
 
const box = new Box();
box.value = "Gameboy";
 
box.value;
(property) Box<unknown>.value?: unknown
 
if (box.hasValue()) {
box.value;
(property) value: unknown
}
ts
class Box<T> {
value?: T;
 
hasValue(): this is { value: T } {
return this.value !== undefined;
}
}
 
const box = new Box();
box.value = "Gameboy";
 
box.value;
(property) Box<unknown>.value?: unknown
 
if (box.hasValue()) {
box.value;
(property) value: unknown
}