안녕하세요. 김용성입니다.
지난 포스팅에서 타입 추론, 타입 명시, 인터페이스 등에 대해 알아보는 시간을 가졌는데요.
이번 포스팅에서는 열거형과 리터럴 타입, 그리고 유니언 타입, 타입 가드 등에 대해 설명하도록 하겠습니다!
다음과 같은 Singer 인터페이스가 존재한다고 보겠습니다.
// app.ts
interface Singer:{
readonly Id:number,
name:string,
company:string,
age?:number,
gender: string,
genre: string,
}
자 여기서 gender라는 성별을 나타내는 필드는 'Female' 혹은 'Male' 값을 가지겠죠?
이렇게 2가지의 값으로 제한하고 싶을때, string 값으로 이를 선언하는 것 외에 다른 여러가지 방법이 존재합니다.
그 중 첫번째로 설명드릴 것은 열거형(enum)인데요.
열거형은 '연관된 아이템들을 함께 묶어서 표현할 수 있는 수단' 이라고 생각하면 됩니다.
아래 예시를 보여드리도록 하겠습니다.
// app.ts
enum GenderType{ // 열거형 선언
Male,
Female
}
interface Singer:{
readonly Id: number,
name:string,
company:string,
age?:number,
gender: GenderType, // string이 아닌 GenderType이라는 custom type을 넣어줌
genre: string,
}
function getSingerDetails(Id:number):Singer{
return{
Id: 2222,
name:'ksy',
company:'M&S'
age:26,
gender: GenderType.Female, // 'Female'이라고 입력할 시 에러 발생
genre:'Jazz'
}
}
열거형은 위 코드처럼 사용하면 됩니다.
enum을 통해서 GenderType이라는 열거형 type을 선언하고 interface 내부의 string type 또한 GenderTyped으로 바꾸어 준 후, 객체를 반환/선언할 때도 GenderType.${value}로 선언해주면 됩니다. 매우 쉽죠?
Interface의 경우, js 파일에서는 보이지 않았는데요.
열거형의 경우에는 코드가 구현되어 js 파일 상에서도 보입니다. 이는 열거형이 런타임에 존재하는 실제 객체이기 때문인데요. 아마 다음과 같이 보일 겁니다.
// app.js
var GenderType;
(function (GenderType) {
GenderType[GenderType["Male"]=0]="Male";
GenderType[GenderType["Female"]=0]="Female";
})(GenderType || (GenderType={}));
'Male'은 0의 값을 가지고, 'Female'은 1의 값을 가진다는 것을 알 수 있습니다.
이를 바로 TypeScrilt의 Numeric Enum 즉, 숫자 열거형이라고 합니다.
enum의 default는 숫자 열거형으로 표현되는데요. 만약 GenderType이 numeric이 아닌 string으로 표현되기를 원한다면 다음과 같이 변경해주면 됩니다.
// app.ts
enum GenderType{ // 열거형 선언
Male='male',
Female='female'
}
이렇게 TypeScript 상에서 변경을 해줄 경우
// app.js
var GenderType;
(function (GenderType) {
GenderType["Male"]="male";
GenderType["Female"]="Female";
})(GenderType || (GenderType={}));
app.js 파일에서 또한 변경된 것을 보실 수 있습니다. 이러한 경우 숫자 열거형 때 처럼, GenderType에 속하는 값이 늘어날 때 자동적으로 고유의 numeric값을 부여하는 기능은 사용할 수 없지만, 보다 코드의 가독성이 좋아진다는 장점이 있습니다.
이번에는 또 다른 방법인 리터럴 타입에 대해 알아보도록 하겠습니다.
리터럴 타입의 경우 열거형보다 훨씬 더 간단합니다.
아래의 코드를 살펴볼까요?
// app.ts
interface Singer:{
readonly Id:number,
name:string,
company:string,
age?:number,
gender: 'male' | 'female',
genre: string,
}
너무나도 간단한 방식이죠? 개인적으로 필자는 해당 필드에 들어오는 값이 2개일 경우에는 enum보다 literal 방식을 사용하는 것이 훨씬 더 합리적이라고 생각합니다.
호출할 때도 아래와 같이 하면 됩니다.
// app.ts
function getSingerDetails(Id:number):Singer{
return{
Id: 2222,
name:'ksy',
company:'M&S'
age:26,
gender: 'female',
genre:'Jazz'
}
}
VS 코드를 사용하는 경우에는 아마 female, male에 대해 자동완성 기능이 적용될 것입니다.
이번에는 any Type에 대해 배워보도록 하겠습니다.
any 에는 단어의 의미 답게 모든 값을 할당할 수 있습니다.
다음과 같은 코드가 있다해도 에러없이 받아들여지죠.
// app.ts
let value: any = 100; //number
value= 'ysKim'; // string
value= true; // boolean
✋ 굉장히 편리하네요? 그렇다면 모든 것을 다 any로 할당할 수 있게 하면 편하지 않을까요?
물론 안되는 것은 아닙니다.
다만 우리가 TypeScript를 사용하는 이유에 대해서 잊어버려서는 안됩니다.
TypeScript에서는 타입에 관한 더 많은 정보를 명시할수록 더더욱 좋아요.
왜냐하면 이는 효율적인 코드의 유지 보수와 직접적으로 연관되기 때문이죠.
✋ 그렇다면 any Type의 존재 이유는 뭘까요?
any Type은 작업중인 코드의 타입 명시가 매우 어려운 경우(ex:서드 파티 라이브러리에서 동적 컨텐츠를 가지고 왔기에 프로그램 실행 시에는 변수의 타입을 알 수 없어 타입지정이 어려운 경우)에만 사용됩니다.
✋ 무슨 타입이 변수에 들어올지는 정확히 알 수 없는데 변수의 타입이 number 아니면 string임이 확실한 경우에는 어떻게 해야할까요?
네 이런 경우에 사용되는 것이 있습니다. 바로 Union Type인데요. 사용법이 아주 간단합니다.
// app.ts
let value: number | string
예시를 적고서도 상당히 민망하네요.
정말 쉽죠? 앞서 배운 리터럴과 굉장히 비슷하게도 '|' 기호를 사용하면 됩니다. 이렇게 명시를 할 경우에는 value에는 number type과 string type 둘 중 어떤 것이 들어와도 에러가 발생하지 않으며 서로가 서로에게로 재할당이 가능하답니다.
✋ 만약 음.. 저런 number혹은 string값을 할당받는 변수가 여러개일 경우에 그때마다 Union type을 사용하면 코드가 너무 안 예쁠 것 같아요...
맞아요. 코드가 지저분하겠죠. 또한 반복이 난무한 코드는 결코 좋은 코드라고 하지 못하죠. 이럴 때는 바로 이것을 사용하면 됩니다.
같은 Union Type이 반복될 경우에는 Type Aliases라는 것을 사용할 수 있습니다.
// app.ts
let value: number | string;
let value2: number | string;
let value3: number | string;
위 코드가 반복되는 Union Type이 난무하는 우리가 우려한 상황이죠.
이를 Type Aliases를 통해 정리하도록 하겠습니다.
// app.ts
type=StrOrNum = number | string;
let value: StrOrNum;
let value2: StrOrNum;
let value3: StrOrNum;
StrOrNum이라는 custom Type을 선언해 준 뒤에 Union Type을 할당하고, 이를 재사용 하면됩니다. 이름이 거창할 뿐, 반복되는 코드를 재활용해주는 간단한 작업이라고 할 수 있습니다. (StrOrNum은 string값 혹은 number값이 들어온다는 의미를 살리기 위한 제가 붙인 이름입니다.)
추가적으로 몇가지 더 설명드리도록 하겠습니다.
타입 가드란 Union Type을 사용할 때 발생할 수 있는 에러에 대해 typeof operator를 사용해서 미연에 방지하는 것을 의미합니다. 아래의 코드를 살펴보겠습니다.
//app.ts
type=StrOrNum = number | string;
let albumCount: number;
const setAlbumCount=(count: StringOrNum):void=>{
albumCount:price;
}
setAlbumCount(50) // 에러 발생
albumCount의 Type은 number이고 setAlbumCount의 argument인 count의 Type은 StringOrNum Type이기 때문에 setAlbumCount의 argument로 string이 아닌 number을 넣어주어도 다음과 같은 에러가 발생할 것입니다.
Type 'StringOfNum' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'
이제 typeof operator와 조건문을 사용해서 이러한 상황을 방지해보도록 하겠습니다.
//app.ts
type=StrOrNum = number | string;
let albumCount: number;
const setAlbumCount=(count: StringOrNum):void=>{
if(typeof count==='string'){
albumCount: 0; //count가 string type일 경우 count값은 0으로 할당
}else{
albumCount: price;
}
}
setAlbumCount(50) // 에러 발생
이런 식으로 Type Guard를 구현할 수 있습니다.
Union Type을 사용할 때 Type Guard는 반드시 알아두는 것이 좋습니다.
https://www.typescriptlang.org/docs/handbook/advanced-types.html
해당 링크 공식문서에 Type Guard에 자세히 나와있으니 한번 보시는 것도 추천드립니다:)
이번 포스팅에서는 쉽지만 너무나 중요한 내용들에 대해 배워보았습니다.
어느덧 TypeScript 개론 시리즈도 끝이보이네요 :)
다음 포스팅 때 뵙도록 하겠습니다!