๐ก Type guard (ํ์ ๋ณดํธ): ์ ํํ ์๋ฏธ ์ ๋ฌ์ ์ํด โtype guardโ๋ ํ๊ธ๋ก ๋ฒ์ญํ์ง ์๋๋ค.
Type guard๋ฅผ ์ฌ์ฉํ๋ฉด ์กฐ๊ฑด๋ฌธ์์ ๊ฐ์ฒด์ ํ์ ์ ์ขํ๋๊ฐ ์ ์๋ค.
TypeScript๋ JavaScript์ instaceof, typeof ์ฐ์ฐ์๋ฅผ ์ดํดํ ์ ์๋ค. ์ฆ ์กฐ๊ฑด๋ฌธ์ typeof์ instanceof๋ฅผ ์ฌ์ฉํ๋ฉด, TypeScript๋ ํด๋น ์กฐ๊ฑด๋ฌธ ๋ธ๋ก ๋ด์์๋ ํด๋น ๋ณ์์ ํ์ ์ด ๋ค๋ฅด๋ค๋ ๊ฒ(=์ขํ์ง ๋ฒ์์ ํ์ )์ ์ดํดํ๋ค. ์๋ ์์๋ฅผ ๋ณด๋ฉด TypeScript๋ ํน์ ๋ฉ์๋(String.prototype.substr)๊ฐ string์ ์กด์ฌํ์ง ์๋๋ค๋ ์ฌ์ค์ ์ธ์ํด ์ฌ์ฉ์ ์คํ๊ฐ ์์ ์ ์์์ ์ง์ ํ๊ณ ์๋ค.
function doSomething(x: number | string) {
if (typeof x === 'string') {
console.log(x.subtr(1)); // TypeScript๋ 'subtr'์ 'string'์ ์กด์ฌํ์ง ์์์ ์๊ณ ๊ฒฝ๊ณ ๋ฅผ ๋ด๋ณด๋ธ๋ค
console.log(x.substr(1));
}
x.substr(1); // Error -> x๊ฐ 'string'์ด๋ผ๋ ๋ณด์ฅ์ด ์๋ค.
}
class Foo {
foo = 123;
common = '123';
}
class Bar {
bar = 123;
common = '123';
}
function doStuff(arg: Foo | Bar) {
if (arg instanceof Foo) {
console.log(arg.foo); //
console.log(arg.bar); // Error! -> Foo์๋ bar๊ฐ ์๋ค.
}
if (arg instanceof Bar) {
console.log(arg.foo); // Error!
console.log(arg.bar); // ใ
ใ
}
console.log(arg.common); // ใ
ใ
console.log(arg.foo); // Error!
console.log(arg.bar); // Error!
}
doStuff(new Foo());
doStuff(new Bar());
TypeScript๋ else ๋ํ ์ดํดํ๊ธฐ ๋๋ฌธ์ ์ฐ๋ฆฌ๊ฐ if๋ฌธ์ผ๋ก ํ์ ์ ํ๋ ์ขํ๋ด๋ฉด, else๋ฌธ ์์ ๋ณ์ ํ์ ์ ์ ๋ ๋์ผํ ํ์ ์ด ๋ ์๋ ์์ ์ ์ธ์งํ๋ค. ์๋ ์์๋ฅผ ๋ณด์.
class Foo {
foo = 123;
}
class Bar {
bar = 123;
}
function doStuff(arg: Foo | Bar) {
if (arg instanceof Foo) {
console.log(arg.foo); // ใ
ใ
console.log(arg.bar); // Error!
} else { // if์์ ์ด๋ฏธ Foo์ผ๋๋ฅผ ๊ฑธ๋ ์ผ๋ Bar์ด๊ฒ ๊ตฐ
console.log(arg.foo); // Error!
console.log(arg.bar); // ใ
ใ
}
}
doStuff(new Foo());
doStuff(new Bar());
in์ ๊ฐ์ฒด ๋ด๋ถ์ ํน์ property๊ฐ ์กด์ฌํ๋์ง๋ฅผ ํ์ธํ๋ ์ฐ์ฐ์๋ก type guard๋ก ํ์ฉํ ์ ์๋ค.
interface A {
x: number;
}
interface B {
y: string;
}
function doStuff(q: A | B) {
if ('x' in q) {
// q: A
} else {
// q: B
}
}
๋ฆฌํฐ๋ด ๊ฐ์ ๊ฒฝ์ฐ === / == / !== / != ์ฐ์ฐ์๋ฅผ ์ฌ์ฉํด ํ์
์ ๊ตฌ๋ถํ ์ ์๋ค.
type TriState = 'yes' | 'no' | 'unknown';
function logOutState(state: TriState) {
if (state == 'yes') {
console.log('์ฌ์ฉ์๊ฐ yes๋ฅผ ๊ณจ๋์ต๋๋ค');
} else if (state == 'no') {
console.log('์ฌ์ฉ์๊ฐ no๋ฅผ ๊ณจ๋์ต๋๋ค');
} else {
console.log('์ฌ์ฉ์๊ฐ ์์ง ๊ฒฐ์ ์ ๋ด๋ฆฌ์ง ์์์ต๋๋ค');
}
}
์ด๋ union ํ์ ์ ๋ฆฌํฐ๋ด์ด ์๋ ๊ฒฝ์ฐ์๋ ๋์ผํ๊ฒ ์ ์ฉ๋๋ค. union ํ์ ์ ๊ณตํต property ๊ฐ์ ๋น๊ตํด union ํ์ ์ ๊ตฌ๋ถํ ์ ์๋ค.
type Foo = {
kind: 'foo', // ๋ฆฌํฐ๋ด ํ์
foo: number
}
type Bar = {
kind: 'bar', // ๋ฆฌํฐ๋ด ํ์
bar: number
}
function doStuff(arg: Foo | Bar) { // Foo, Bar ๊ณตํต ํ์
์ด kind์ด๋๊น ์ด๊ฑธ๋ก ๋น๊ต
if (arg.kind === 'foo') {
console.log(arg.foo); // okay
console.log(arg.bar); // Error!
} else {
console.log(arg.foo); // Error!
console.log(arg.bar); // okay
}
}
TypeScript๋ a == null / != null๋ก null๊ณผ undefined ๋ชจ๋ ๊ฑธ๋ฌ๋ผ ๋งํผ ์๊ฒฉํ๋ค.
function foo(a?: number | null) {
if (a == null) return;
// ์ด์ ๋ถํฐ a๋ ๊ฐ์ด ์๋ค๋ฉด ๋ฌด์กฐ๊ฑด number์ด๋ค.
}
JavaScript ์ธ์ด๋ ํ๋ถํ ๋ฐํ์ ๋ด๋ถ ๊ฒ์ฌ (=runtime introspection support)๋ฅผ ์ง์ํ์ง ์๋๋ค. ์ผ๋ฐ JavaScript ๊ฐ์ฒด(๊ตฌ์กฐ์ ํ์ structural typings ํ์ฉ)๋ฅผ ์ฌ์ฉํ ๋์๋ instanceof๋ typeof์ ๊ฐ์ ์ฐ์ฐ์๋ฅผ ์์ธ์ค์กฐ์ฐจ ํ ์ ์๋ค. ํ์ง๋ง TypeScript์์๋ ์ฌ์ฉ์ ์ ์ Type Guard ํจ์๋ฅผ ๋ง๋ค์ด ์ด๋ฅผ ํด๊ฒฐํ ์ ์๋ค. ์ฌ์ฉ์ ์ ์ Type Guard ํจ์๋ ๋จ์ํ ์ด๋ค ์ธ์๋ช ์ ์ด๋ ํ ํ์ ์ด๋ค ๋ผ๋ ๊ฐ์ ๋ฆฌํดํ๋ ํจ์์ผ ๋ฟ์ด๋ค.
/**
* ์ผ๋ฐ์ ์ธ ์ธํฐํ์ด์ค ์์
*/
interface Foo {
foo: number;
common: string;
}
interface Bar {
bar: number;
common: string;
}
/**
* ์ฌ์ฉ์ ์ ์ Type Guard!
*/
function isFoo(arg: any): arg is Foo {
return arg.foo !== undefined;
}
/**
* ์ฌ์ฉ์ ์ ์ Type Guard ์ฌ์ฉ ์์
*/
function doStuff(arg: Foo | Bar) {
if (isFoo(arg)) {
console.log(arg.foo); // okay
console.log(arg.bar); // Error!
} else {
console.log(arg.foo); // Error!
console.log(arg.bar); // okay
}
}
doStuff({ foo: 123, common: '123' });
doStuff({ bar: 123, common: '123' });