TypeScript
에서 Narrowing
은 특정 타입으로 제한하는 작업을 의미합니다. 이를 통해 코드 내에서 더 안전한 작업이 가능해지고, 오류를 줄일 수 있습니다.
TypeScript
의 narrowing
은 크게 두 가지 종류가 있습니다.
Type Inference
)타입 추론은 컴파일러가 코드를 분석하여 변수나 매개변수의 타입을 자동으로 추론해 주는 방식입니다.
타입 추론에 의해 좁혀진 타입을 활용하여 코드를 작성할 수 있습니다.
타입 추론이 narrowing
의 일종인 이유는 타입 추론 과정에서 컴파일러가 가능한 한 세부적인 타입으로 추론하려고 하기 때문입니다.
이러한 추론 과정에서 컴파일러는 다양한 방법으로 narrowing
을 수행합니다.
예를 들어, union
타입에서 해당 변수가 어떤 타입으로 세부화되는지 결정하려면, 해당 변수를 사용하는 곳에서 사용된 연산자와 연산 결과에 따라 가능한 한 좁은 범위의 타입으로 추론합니다.
Type Guards
)타입 가드는 타입 추론에 의한 자동으로 추론되는 타입을 바꾸기 위한 용도로 사용됩니다.
Type Guards
는 런타임에 값의 타입을 검사하여 해당 값의 타입을 좁혀주는 방식입니다.
typeof
, instanceof
, in
, is
등의 연산자나 사용자 정의 함수를 사용할 수 있습니다.
타입 가드는 특정 조건이 참일 때 타입을 확정하는 TypeScript
의 기능입니다. 예를 들어, typeof
연산자를 사용하여 값의 타입을 검사하거나, 클래스의 인스턴스 여부를 확인하는 instanceof
연산자 등을 사용하여 타입을 좁힐 수 있습니다.
TypeGuard는 여러 종류가 있습니다.
다음은 typeof
연산자를 사용한 예시입니다.
function printNumberOrString(value: number | string) {
if (typeof value === 'number') {
console.log(`The input is a number: ${value}`);
} else {
console.log(`The input is a string: ${value}`);
}
}
printNumberOrString(123); // The input is a number: 123
printNumberOrString('hello'); // The input is a string: hello
위 코드에서 value
매개변수는 number
또는 string
타입일 수 있습니다.
typeof
연산자를 사용하여 value
의 타입이 number
인지 검사하고, value
의 타입이 number
일 때는 The input is a number
와 입력된 value
를 출력하고, value
의 타입이 number
가 아닐 때는 The input is a string
와 입력된 value
를 출력합니다.다음은 instanceof
연산자를 사용한 예시입니다.
class Animal {
constructor(public name: string) {}
}
class Dog extends Animal {
bark() {
console.log('Woof!');
}
}
class Cat extends Animal {
meow() {
console.log('Meow!');
}
}
function makeSound(animal: Animal) {
if (animal instanceof Dog) {
animal.bark();
} else if (animal instanceof Cat) {
animal.meow();
} else {
console.log('This animal is silent');
}
}
const myDog = new Dog('Max');
const myCat = new Cat('Kitty');
const myAnimal = new Animal('Unknown');
makeSound(myDog); // Woof!
makeSound(myCat); // Meow!
makeSound(myAnimal); // This animal is silent
위 코드에서 makeSound
함수는 Animal
클래스를 상속받은 Dog
와 Cat
클래스의 인스턴스를 매개변수로 받습니다.
instanceof
연산자를 사용하여 animal
매개변수가 Dog
클래스의 인스턴스인지 또는 Cat
클래스의 인스턴스인지 검사하고, animal
이 Dog
클래스의 인스턴스일 때는 bark
메소드를 호출하여 Woof!
를 출력하고, animal
이 Cat
클래스의 인스턴스일 때는 meow
메소드를 호출하여 Meow!
를 출력합니다. animal
이 Dog나 Cat
클래스의 인스턴스가 아닐 때는 This animal is silent
을 출력합니다.
Nullable Type Guard
는 어떤 변수가 null
또는 undefined
일 수도 있는 경우, 해당 변수가 null
또는 undefined
가 아닌지를 확인하는 Type Guard
입니다.
예를 들어, 다음과 같이 함수가 인자로 전달받은 변수가 null
또는 undefined
일 수도 있는 경우를 가정해봅시다.
function printLength(str: string | null | undefined) {
if (str !== null && str !== undefined) {
console.log(`The length of the string is ${str.length}.`);
} else {
console.log('The string is null or undefined.');
}
}
위 함수는 string
, null
, undefined
타입 중 하나를 인자로 받습니다.
그리고 str
이 null
또는 undefined
이 아닐 때, 문자열의 길이를 출력합니다.
str이 null
또는 undefined
인 경우, "The string is null or undefined."
를 출력합니다.
이 함수에서 사용된 if (str !== null && str !== undefined)
구문은 Nullable Type Guard
입니다.
이 구문은 str
변수가 null
또는 undefined
가 아닌지를 확인하고, 그 결과에 따라 다른 코드를 실행합니다.
이렇게 Type Guard
를 사용하면 str
변수가 string
타입이라는 것이 확실하므로, 이후 코드에서 str
변수를 자유롭게 사용할 수 있습니다.
interface Car {
make: string;
model: string;
}
interface Truck {
make: string;
model: string;
payload: number;
}
function isCar(vehicle: Car | Truck): vehicle is Car {
return 'payload' in vehicle === false;
}
function printVehicle(vehicle: Car | Truck) {
if (isCar(vehicle)) {
console.log(`This is a car: ${vehicle.make} ${vehicle.model}`);
} else {
console.log(`This is a truck: ${vehicle.make} ${vehicle.model} with a payload of ${vehicle.payload}lbs`);
}
}
위의 코드에서 isCar
함수는 vehicle
이 Car
타입인지를 판단하며, 반환값으로 vehicle
is Car
형태를 사용합니다. printVehicle
함수에서는 isCar
함수를 이용하여 vehicle
이 Car
타입인지 확인하고, 해당 타입에 맞는 문자열을 출력합니다.
Custom Type Guard
는 다음과 같이 TypeScript
에서 사용할 수 있는 사용자 정의 함수로, 매개변수로 전달된 객체가 특정 타입이라는 것을 검사하여 그 결과에 따라서 해당 객체를 다루는 코드를 작성하는 것입니다.
interface Cat {
name: string;
meow(): void;
}
interface Dog {
name: string;
bark(): void;
}
function isCat(pet: Cat | Dog): pet is Cat {
return (pet as Cat).meow !== undefined;
}
function petSounds(pet: Cat | Dog) {
if (isCat(pet)) {
pet.meow();
} else {
pet.bark();
}
}
위의 예시 코드에서 isCat
함수는 pet
매개변수가 Cat
타입인지 검사하고, 반환 타입으로 pet is Cat
를 사용합니다. 이렇게 반환된 pet is Cat
는 해당 함수의 반환값으로, pet
매개변수가 Cat
타입일 경우 true
, Dog
타입일 경우 false
가 되며, 이는 if
문에서 타입 가드로 활용됩니다.
따라서, petSounds
함수에서 isCat
함수를 사용하여 pet 매개변수가 Cat
타입인지 검사하고, if
문에서 그 결과에 따라서 pet.meow()
또는 pet.bark()
메서드를 호출하는 코드를 작성하고 있습니다.
이러한 방식으로 Custom Type Guard
를 사용하여 해당 객체가 특정 타입인지를 검사하여, 타입에 맞는 코드를 작성할 수 있습니다.
narrowing
은 코드의 가독성과 안정성을 높일 수 있는 도구 중 하나이며, 유용한 경우가 많습니다.
하지만, 경우에 따라서는 코드를 복잡하게 만들기도 하고, 필요 없는 경우도 있을 수 있습니다.
narrowing
을 사용할 때 주의해야 할 점은, 항상else
블록이 필요하다는 것입니다.
만약 if
블록에서 타입 가드를 사용하고 그 이후에 else
블록이 없다면, 컴파일러는 타입이 any
로 추론될 가능성이 있습니다.
이를 방지하기 위해서는 항상 else
블록을 추가해주어야 합니다.
narrowing
을 과도하게 사용하면 코드의 가독성이 떨어질 수 있습니다.
너무 많은 if
문이나 switch
문이 코드에 존재하면 코드의 복잡성이 증가할 수 있습니다.
이러한 경우, 코드를 분리하거나 함수를 만들어서 가독성을 향상시키는 것이 좋습니다.