다음과 같은 함수가 있다
function padLeft(padding: number | string, input: string): string{
throw new Error("Not implemented yet!");
}
padding
이 number면 input앞에 공백을 갯수만큼 붙일 것이고 string이면 그냥 input앞에 붙이고 싶다면 아래와 같이 수정한다.
function padLeft(padding: number | string, input: string) {
if (typeof padding === "number") {
return " ".repeat(padding) + input;
}
return padding + input;
}
타입스크립트가 자바스크립트에서 type을 안전하게 사용하려고 만든 타입시스템의 목적을 기억해라.
if문 안에서 padding의 타입은 number로 특정지어진다.
그리고 if문 밖에서는 string으로 특정지어진다.
function padLeft(padding: number | string, input: string) {
if (typeof padding === "number") {
console.log(typeof padding) // number
return " ".repeat(padding) + input;
}
console.log(typeof padding) // string
return padding + input;
}
typeof
type guardstypeof 연산자는 null
을 반환하지 않는다.
function printAll(strs: string | string[] | null) {
if (typeof strs === "object") {
for (const s of strs) { //Object is possibly 'null'.
console.log(s);
}
} else if (typeof strs === "string") {
console.log(strs);
} else {
// do nothing
}
}
자바스크립트에서는 array타입도 object 타입이기 때문에 strs는 object타입이다. 또한 typeof null
의 결과값 역시 "object"
이다.
자바스크립트에서는 어떠한 조건연산자도 사용할 수 있다. if구문에서는 !
를 포함해서.
또한 if
문은 조건을 강제로 boolean
으로 만든다.
bigint
버전 0)위의 경우 모두 false로 강제되고 나머지 값들은 모두 true로 강제된다.
이를 확인하고 싶으면 Boolean
함수를 쓰거나 !!
를 쓰면 편하다.
Boolean("hello"); //type:boolean value:true
!!"world"; // type:true value: true
우리는 위의 오류가 났던 코드를 아래와 같이 변경할 수 있다.
function printAll(strs: string | string[] | null){
if (strs && typeof strs === "object"){
for (const s of strs) {
console.log(s);
}
} else if (typeof strs === "string"){
console.log(strs);
}
}
타입스크립트는 switch
문과 ===
,!==
,==
,!=
같은 동등연산자 또한 타입을 좁히기 위해 사용할 수 있다.
function example(x: string | number, y: string | boolean){
if(x === y) {
console.log(typeof x) // string
console.log(typeof y) // string
x.toUpperCase();
y.toUpperCase();
} esle {
console.log(typeof x); // string | number
console.log(typoef y); // string | boolean
}
}
x === y
타입까지 똑같은걸 검사하는 연산자이기 때문에 타입이 특정 지어진다.
in
operator narrowingin 연산자는 자바스크립트 연산자로, 객체가 해당 이름을 가진 property가 있는지 검사해준다.
type Fish = { swim: () => void };
type Bird = { fly: () => void };
type Human = { swim?: () => void, fly?: () => void};
function move(animal: Fish | Bird | Human){
if("siwm" in animal) {
return animal.swim(); // animal: Fish | Human
}
return animal.fly(); // animal: Bird | Human
}
instanceof
narrowinginstanceof
연산자는 다른 값의 instance에 해당 값이 있는지 없는지를 판단해준다.
x instanceof Foo
는 x
가 Foo.prototype에 포함되는지 체크한다.
해당 연산자는 class에서 new
와 함께 생성되었을때 더 유용하다.
function logValue(x: Date | string) {
if (x instanceof Date) {
console.log(x.toUTCString()); // x:Date
} else{
console.log(x.toUpperCase()); // x:string
}
}
// 생성자 정의
function C(){}
function D(){}
var o = new C();
// true, 왜냐하면 Object.getPrototypeOf(o) === C.prototype
o instanceof C;
// false, 왜냐하면 D.prototype이 o 객체의 프로토타입 체인에 없음
o instanceof D;
타입스크립트는 변수를 할당 할 때 오른쪽을 보고 적절하게 type narrowing을 해줌
let x = Math.random() < 0.5 ? 10 : "hello world!"; // x: string | number
x = 1; // x: number
x = "goodbye!"; // x: string
하지만 전혀다른 새로운 타입으로는 할당이 되지 않음
let x = Math.random() < 0.5 ? 10 : "hello world!"; // x: string | number
x = 1; // x: number
x = true; // Type 'boolean' is not assignable to type 'string | number'.
x = "goodbye!"; // x: string
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
pet is Fish
는 type predicates
이다. 예측은 parameterName is Type
형태로 구성된다. parameterName
은 반드시 현재 함수의 파라미터 이름이여야한다.
// Both calls to 'swim' and 'fly' are now okay.
let pet = getSmallPet();
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
우리가 보았떤 예시들은 string, boolean, number같은 단일 변수에 대한 narrowing이었다. 여기서는 조금 더 복잡한 구조를 시도해보겠다.
interface Shape {
kind: "circle" | "square";
radius?: number; // 원에 대한 특성
sideLength?: number; // 사각형에 대한 특성
}
circle
은 radius
를 가질 것이고 square
은 sideLength
를 가질 것 이다.
kind에 string을 쓰는 대신 "circle" | "square"
와 같은 문자열 리터럴을 쓰면 스펠링오류같은 걸 방지할 수 있다.
function handleShape(shape: Shape) {
// oops!
if (shape.kind === "rect") { //This condition will always return 'false' since the types '"circle" | "square"' and '"rect"' have no overlap.
// ...
}
}
function getArea(shape: Shape) {
return Math.PI * shape.radius ** 2; //Object is possibly 'undefined'.
}
radius
는 optional이기때문에 number | undefined
이다. 여기서 kind를 지정해준다고 한들 여전히 같은 오류일 테지만 우리는 non-null assertion
을 사용할 수 있다.
function getArea(shape: Sahpe){
if (shape.kind === "circle"){
return Math.PI * shape.radius! ** 2;
}
}
이 방법은 이상적이지 않다. 이럴때는 차라리 다음과 같이 바꾸는 것이 낫다.
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2; // shape: Circle
}
}
never
타입은 어떤 타입에도 할당 가능하지만 어떤 타입도 never
타입에 할당할 수 없다.
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
Shape
union에 추가를 하면 에러가 난다.
interface Triangle {
kind: "triangle";
sideLength: number;
}
type Shape = Circle | Square | Triangle;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape; // Type 'Triangle' is not assignable to type 'never'.
return _exhaustiveCheck;
}
}
이럴 땐 case에 추가해주면 나지 않는다.
잘 읽고 갑니다 :)