다음과 같은 함수가 있다
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에 추가해주면 나지 않는다.
잘 읽고 갑니다 :)