유니언 타입은 두개 이상의 다른 타입을 조합해 만든 타입이다. 유니언 타입을 만들기 위해 조합한 타입들을 각각 유니언 타입의 멤버라고 한다.
function combine(input1: string | number, input2: string | number) {
let result;
if (typeof input1 == "number" && typeof input2 == "number") {
// 유니언 타입을 이용해 함수를 구현한 후 런타임 타입 체크를 통해 argument들의 type의 종류에 따라 유연하게 기능을 구현할 수 있다.
result = input1 + input2;
} else {
result = input1.toString() + input2.toString();
}
return result;
}
const combineNumber = combine(1, 44);
console.log(combineNumber); // => 45
const conbineName = combine("변", "진상");
console.log(conbineName); // => 변진상
리터럴 타입은 단순히 특정 변수나 매개변수가 아니다. 정확한 값을 가지는 타입이다. 일반적인 number, string, object... 등과 같은 타입 이외에도, 구체적인 문자열과 숫자 값을 타입 위치에서 지정할 수 있다.
위 사진과 같이 cnstNumb라는 변수는 타입할당을 하지 않았는데, const 키워드로 선언한 값이 변하지 않는 number값이다. 그래서 TS에서는 이를 2.8이라는 리터럴이 타입으로 지정된 것을 볼 수 있다.
리터럴 타입 자체는 별 의미가 없을 수 있으나, 유니언과 같이 쓰인다면 효과적으로 사용될 수 있다.
만약 함수 선언 당시 리터럴 타입으로 주어진 "string"이나 "number"타입이 아닌 argument가 주어질 경우 경고를 띄운다.
Boolean 타입도 리터럴타입이다. Boolean 리터럴 타입의 경우 true, false 단 두개의 타입만이 존재한다. Boolean 타입은 true | false 유니언타입의 별칭(alias)이다.
Type alias를 사용하면 타입의 별칭을 줄 수 있다. 다음과 같이 일반적인 core type의 별칭을 줄수 있다.
type NumberNumber = number;
그러나 단순히 한가지의 타입에 별칭을 지어 사용할 경우 코드를 쓰고 읽어감에 큰 혼란을 줄 수 있기 때문이 지양한다.
type alias는 특히 union type과 literal type의 조합에서 빛을 발한다.
function combineFunc(
input1: string | number,
input2: string | number,
to: "toStr" | "toNumb"
) {
if (to === "toStr") {
return input1.toString() + input2.toString();
} else {
return +input1 + +input2;
}
}
위 코드의 경우 union type으로 string | number가 반복되고 있다. 단 두 번만 쓰일 경우는 별 무리가 없어보이지만 만약 1억번 사용한다면 의미가 있을 것이다.
type Combinable = string | number;
type ToTo = "toStr" | "toNumb";
function combineFunc(input1: Combinable, input2: Combinable, to: ToTo) {
if (to === "toStr") {
return input1.toString() + input2.toString();
} else {
return +input1 + +input2;
}
}
이렇게 코드를 간결하게 정리 할 수 있을 것이다.
전개구문을 이용해 응용할 수 있다.
type Role = "teacher" | "student" | "parents" | "dog";
const jack: { name: string; role: [Role, ...Role[]] } = {
name: "Jack",
role: [
"teacher",
"student",
"parents",
"parents",
"parents",
"parents",
"dog",
],
};
console.log(jack);
타입 별칭을 이용해 객체타입에도 별칭을 붙일 수 있다.
/* -------------------------------------------------------------------------- */
/* Type Alias를 이용한 객체 타입 간소화 */
/* -------------------------------------------------------------------------- */
type User = { name: string; age: number };
function getUserStatus(userObj: User) {
return console.log(`name: ${userObj.name}, age: ${userObj.age}`);
}
const jack: User = {
name: "잭",
age: 1111,
};
console.log(getUserStatus(jack));
이렇게도 사용 가능...
function add(num1: number, num2: number): number {
return num1 + num2;
}
// number라고 명시할 수 있지만 TS가 add 함수가 number를 리턴함을 추론할 수 있기 때문에 생략하는 것도 좋다.
function printResult(num: number): void {
console.log(num);
}
// 이 경우 undefined를 return하는데, void는 함수가 undefined나 아무것도 return하지 않음을 명시한다.
function printResult2(num: number): undefined {
console.log(num);
return;
}
// 이렇게 undefined를 리턴함을 명시할 수 있지만 함수 내에 return;을 작성해줘야 경고를 띄우지 않으며, 이렇게는 거의 사용하지 않는다.
아래 코드와 같이 다를 함수를 지시하는 변수(나는 이해를 위해 함수를 가리키는 pointer라고 했다.)나 Callback에서 사용할 수 있는 Function Type이 있다.
/* -------------------------------------------------------------------------- */
/* 자료형으로서의 Function */
/* -------------------------------------------------------------------------- */
let funcPointer: Function;
funcPointer = add;
funcPointer = printResult;
이렇게 funcPointer가 Funtion 타입의 데이터를 할당할 것임을 명시했기 때문에, 기존에 작성한 add, printResult 둘 다 할당이 가능하다.
하지만 이를 좀 더 엄격하게 할당하기 위해 함수의 명세(parameter의 자료형, return type)를 자료형으로 명시할 수 있다.
/* -------------------------------------------------------------------------- */
/* 더욱 엄격한 자료형 제공을 위한 함수 명세 이용 */
/* -------------------------------------------------------------------------- */
let strictFuncPointer: (a: number, b: number) => number;
strictFuncPointer = add;
// strictFuncPointer = printResult;
// !!! ERROR: 자료형으로 제공한 함수의 명세와 일치하지 않기 때문에 Error !!!
이렇게 에러를 띄운다!
call back 함수의 type의 반환값이 void로 없다고 명시하더라도, 실제로는 callback 함수에서 데이터를 반환할 수 있다.
/* -------------------------------------------------------------------------- */
/* 함수의 인자로 주어지는 Callback function의 자료형 명시 */
/* -------------------------------------------------------------------------- */
function addAndHandle(
num1: number,
num2: number,
cb: (result: number) => void
) {
const addedNum = num1 + num2;
console.log(cb(addedNum)); // 콜백 함수의 반환값이 void라도 값을 반환할 수 있다.
}
const isReturn = addAndHandle(11, 22, (result) => {
console.log(result);
return true;
});
let anyVar: any;
let unknownVar: unknown;
anyVar = 3;
console.log(anyVar.toUpperCase()); // !!! 런타임 ERROR: 컴파일 당시에는 문제가 없다. !!!
console.log(typeof anyVar);
unknownVar = 2;
//console.log(unknownVar.toUpperCase());
// !!! ERROR: 컴파일 전에 unknown 자료형이기 때문에 타입체킹을 더 엄격히 하라고 에러를 띄운다.!!!
if (typeof unknownVar === "string") {
console.log(unknownVar.toUpperCase());
}
// 이렇게 타입체킹을 거치면 에러를 띄우지 않는다.
console.log(typeof unknownVar);
any: 모든 타입의 값이 할당될 수 있다. TS에서 타입체킹을 유연하게 하기 때문에 컴파일 후 브라우저에서 실행하는 런타임시 에러를 띄울 수 있다.(예를 들어 number type의 데이터가 할당된 변수에 string의 메서드를 사용한 경우)
unknown: 모든 타입의 값이 할당될 수 있다. TS에서 타입체킹을 컴파일러가 엄격하게 하기 때문에, 위 코드와 같이 타입체킹을 위한 추가적인 코드를 요구하기도 한다.
never type 에러나 무한루프 등고 같이 코드가 중단 됨에 따라 이후의 코드에 도달할 수 없어 절대 값을 리턴할 수 없는 경우에 사용한다. void와 같지만 never라고 표기함으로써 sementic meaning을 추가해 코드 품질의 관점에서 의도를 더 분명히 할 수 있다.
/* -------------------------------------------------------------------------- */
/* never type */
/* -------------------------------------------------------------------------- */
function genError(message: string, code: number): never {
throw { message: message, errorCode: code };
//무한루프: while(true){}
}
genError("an error occurred!", 500);