728x90

조건부 타입 활용하기

타입스크립트의 조건부 타입은 삼항 연산자와 동일하게 Condition ? A: B 형태를 가지는데 A는 Condition 이 true 일 때, 도출되고 B는 false일 때 도출되는 타입이다.

 

조건부 타입을 활용하면 중복되는 타입 코드를 제거하고 상황에 따른 타입을 얻을 수 있다.

 

1. extends와 제네릭을 활용한 조건부 타입

interface Bank {
  financialCode: string;
  companyName: string;
  name: string;
  fullName: string;
}

interface Card {
  financialCode: string;
  companyName: string;
  name: string;
  appCardType?: string;
}

type PayMethod<T> = T extends 'card' ? Card : Bank;
type CardPayMethodType = PayMethod<'card'>;
type BankMethodType = PayMethod<'bank'>;

 

이렇게 사용할경우 인자에 따라 반환되는 타입을 다르게 설정할 수 있다. (유니온으로 설정할 경우 타입스크립트는 인자에 따라 다른 타입을 정확히 추론할 수 없다.)

extends활용 예시는 아래와 같다.

  1. 제네릭과 extends를 함께 사용해 제네릭으로 받는 타입을 제한 => 따라서 개발자는 잘못된 값을 넘길 수 없어서 휴먼에러가 방지됨

  2. extends를 활용해 조건부 타입 설정함.
    조건부 타입을 사용해서 반환 값을 사용자가 원하는 값으로 구체화 할 수 있음 
    => 불필요한 타입 가드, 타입 단언등을 방지할 수 있음.

2. infer를 활용해서 타입 추론하기

infer는 타입을 추론하는 역할을 하고, 삼항 연산자를 사용한 조건문 형태를 가진다.

type UnpackPromise<T> = T extends Promise<infer K>[] ? K : any;

const promises = [Promise.resolve('Mark'), Promise.resolve(38)];
type Expected = UnpackPromise<typeof promises>; //type Expected = string | number

 

템플릿 리터럴 타입 활용하기

전장에서 언급된 템플릿 리터럴 타입은 언제 쓸지가 모호했는데, 배민외식업광장에 적용한 예시를 통해 알아보자.

type Direction =
  | 'top'
  | 'topLeft'
  | 'topRight'
  | 'bottom'
  | 'bottomLeft'
  | 'bottomRight';

type Vertical = 'top' | 'bottom';
type Horizon = 'left' | 'right';

type Direction2 = Vertical | `${Vertical}${Capitalize<Horizon>}`;

 

일일이  입력하는 것보다 가독성도 좋고 재사용 수정도 용이하다. 또한 변수에 할당되는 타입을 특정 문자열로 정확히 검사하여 휴먼 에러를 방지하고, 자동 완성 기능을 통해 생산성도 높인다.

 

다만 유니온 타입의 경우 조합이 너무 복잡할 경우에는 타입스크립트 컴파일러가 추론하지 않고 에러를 뱉을 수 있어서, 적절히 나누는게 좋다..

커스텀 유틸리티 타입

Pick과 Omit 같은 유틸리티 타입을 활용하면, 중복된 코드를 작성하지 않아도 되고 유지보수도 편리해진다.

이런 유틸리티 타입처럼 커스텀한 유틸리티 타입을 만들 수 있다.

1. PickOne 유틸리티 함수

type CustomCard = {
  type: 'card';
  card: string;
};

type CustomAccount = {
  type: 'account';
  account: string;
};

function withdraw(type: CustomCard | CustomAccount) {
  //...
}

withdraw({ type: 'card', card: 'hyundai' });

앞선 장에서 유니온 타입으로 할 경우, 둘 중 하나의 타입만 존재해야 하는 경우를 고를 수 없어서 에러가 뜰 수 있고, 이를

해결하기 위한 방법으로 식별할 수 있는 유니온을 사용한다 했다.

하지만 이미 작성된 코드에 위와 같은 방식을 적용하려하면, 많은 부분을 수정해야 한다.

이러한 경우를 위해 PickOne이라는 커스텀 유틸리티 함수를 만들려고 한다.

 

목표 => account : string 혹은 card:string 중 하나만 가진 객체만 허용해야 함.

account 일 떄는 card를 받지 못하고, card 일 떄는 account를 받지 못하게, 다른 타입을 옵셔널한 undefined값으로 지정하는 방식을 사용할 것이다.

type CustomEx1 =
  | { account: string; card?: undefined }
  | { card: string; account?: undefined };

이를 아래와 같은 커스텀 유틸리티 타입을 통해 만들 수 있다.

 

type PickOne<T> = {
  [P in keyof T]: Record<P, T[P]> &
    Partial<Record<Exclude<keyof T, P>, undefined>>;
}[keyof T];

 

위 커스텀 유틸리티 타입은 두가지로 나누어 생각할 수 있다.

#1) One 타입

type exCard = { card: string };

type One<T> = { [P in keyof T]: Record<P, T[P]> }[keyof T];

const one: One<exCard> = { card: 'hyunadi' }; //const one: Record<"card", string>

 

1)  [P in keyof T] : P는 T객체의 키값

2)  Record<P, T[P]>  : P타입을 키로 가지고, value는 P를  키로둔 T객체의 값의 레코드 타입

3)  { [P in keyof T]: Record<P, T[P]> } : 키는 T객체의 키 모음, values는 해당 키의 원본 객체 T

4) 3번타입에서 다시 [keyof T] 키 값으로 접근

 

#2) ExclueOne<T> 타입

type ExcludeOne<T> = {
  [P in keyof T]: Partial<Record<Exclude<keyof T, P>, undefined>>;
}[keyof T];

 

1)  [P in keyof T] : P는 T객체의 키값

2) Exclude<keyof T, P :  T객체가 가진 키 값에서 P 타입과 일치하는 키 값을 제외 (이를 A라고 하자)

3) Record<Exclude<keyof T, P>, undefined> : 키로 A 타입, 값으로는 undefined를 갖는 레코드 타입(B라고 하자)

4) Partial<Record<Exclude<keyof T, P>, undefined>> : B 타입을 옵셔널로 만듬

5) [P in key of T] 로 매핑된 타입에서 동일한 객체의 키값인 [keyof T] 로 접근하기에 4번 타입이 반환된다.

 

즉 두 함수를 합치면 PickOne이 된다.

type PickOne<T> = One<T> & ExcludeOne<T>;

 

type PickOneCard = {
  card: string;
  test: string;
};

type PickOneAccount = {
  account: string;
};

type CardOrAccount = PickOne<PickOneCard & PickOneAccount>;

function withdrawPick(type: CardOrAccount) {
  //. .
}

withdrawPick({ card: 'hyundaii' });
// withdrawPick({ card: 'hyundaii', account: 'test' });// error

즉 하나의 키값만 뽑아 쓸 수 있다.

 

2. NonNullable 타입 검사 함수를 사용하여 간편하게 타입가드 하기 

type NonNullable<T> = T extends null | undefined ? never : T;

타입 스크립트에서 제공하는 유틸리티 타입으로 제네릭으로 받는 T가 null 또는 undefined 일 떄 never 또는 T를 반환하는 타입이다

이를 활용하여서 타입가드를 효율적으로 할 수 잇는 함수를 만들 수 있다.

function NonNullable<T>(value: T): value is NonNullable<T> {
  return value !== null && value !== undefined;
}

 

불변 객체 타입으로 활용하기

프로젝트에서 상숫값을 관리할 때 객체를 쓴다  ex) theme , 상숫값등등

이때 as const 와 keyof로 객체 타입을 구체적으로 설정하면 타입에 맞지 않는 값을 전달할 경우 타입 에러가 반환되기 떄문에 컴파일 단계에서 발생하는 실수를 방지할 수 있다. 또한 자동 완성 기능을 통해 객체에 어떤 값이 있는지 쉽게 파악할 수 있게 된다.

 

interface ColorType {
  red: string;
  green: string;
  blue: string;
}

type ColorKeyType = keyof ColorType; // "red"|"?green"|"blue"

const colors = {
  red: '#F45452',
  green: '#0C952A',
  blue: '#1A7CFF',
};

type ColorsType = typeof colors; //{    red: string;    green: string;    blue: string;}

 

interface ColorType {
  red: string;
  green: string;
  blue: string;
}

type ColorKeyType = keyof ColorType; // "red"|"?green"|"blue"

const colors = {
  black: '#000000',
  white: '#FFFFFF',
  red: '#F45452',
  green: '#0C952A',
  blue: '#1A7CFF',
};

type ColorsType = typeof colors; //{black: string; white: string; red: string; green: string;blue: string;}

const theme = {
  colors: {
    default: colors.black,
    ...colors,
  },
  backgroundColor: {
    default: colors.white,
    red: colors.red,
    green: colors.green,
    black: colors.black,
  },
};

type ColorType2 = keyof typeof theme.colors; //"black" | "white" | "red" | "green" | "blue" | "default"

 

 

 

Record 원시 타입 키 개선하기

Record를 쓸 떄 string이나 number같은 타입을 명시하는데, 이럴 경우 키가 유효하지 않더라도 오류를 표시하지 않는다.

 

type FoodCategory = string;
interface Food {
  name: string;
}

const foodByCategory: Record<FoodCategory, Food[]> = {
  한식: [{ name: '제육' }, { name: '뚝불' }],
  일식: [{ name: '초밥' }, { name: '텐동' }],
};

foodByCategory['양식']; //Food[] 로 추론
foodByCategory['양식'].map((food) => console.log(food.name)); // Uncaught TypeError

 

 

이때 옵셔널 체이닝으로 런타임 에러를 방지할 수 있지만, 개발자가 일일이 undefined지 확인하고 붙여야하는 번거러움이 생긴다.

foodByCategory['양식']?.map((food) => console.log(food.name));

 

이를 해결하기 위한 방법은 2가지이다.

 

  1. 유닛 타입으로 변경하기
    type UnitTypeCategory = '한식' | '일식';​​
    하지만 키가 무한해야할 상황에는 적합하지 않다.
  2. Partial을 활용해서 정확한 타입 표기
    Partial을 사용하여 해당 값이 undefined일 수 있는 상태임을 표시할 수 있다.
    type FoodCategory = string;
    
    type PartialRecord<K extends string, T> = Partial<Record<K, T>>;
    
    interface Food2 {
      name: string;
    }
    
    const foodByCategory2: PartialRecord<FoodCategory, Food[]> = {
      한식: [{ name: '제육' }, { name: '뚝불' }],
      일식: [{ name: '초밥' }, { name: '텐동' }],
    };
    
    foodByCategory2['양식'].map((food) => console.log(food.name));// Obeject is possibley 'undefined'​
     Partial을 사용할 경우 모든 key값은 optional이 되므로 undefined일 수 있다는 경고가 나와서 옵셔널 체이닝이나 조건문을 개발자가 쓰도록 유도할 수 있다.


 

 

 

728x90

1. 타입 확장하기

타입 확장은 기존 타입을 사용해서 새로운 타입을 정의하는 것이다. 

타입 확장의 장점은 1. 확장성 2. 코드 중복 방지이다

 

  1. 유니온 타입
    2개 이상의 타입을 조합하여 사용하는 방법 (합집합의 개념)
  2. 교차 타입
    기존 타입을 합쳐 필요한 모든 기능을 가진 하나의 타입(교집합의 개념)
    type IdType = string | number;
    type Numeric = number | boolean;
    type Universal = IdType & Numeric; // number​
  3. extends
    유니온과 교차 타입을 사용하여 새 타입을 만드는 것은 type 키워드에서만 가능. 
    interface에서는 extends로 교차 타입을 작성한다.
    interface BaseMenuItem {
      itemName: string | null;
      itemImageUrl: string | null;
    }
    
    interface BaseCartItem extends BaseMenuItem {
      quantity: number;
    }​

    하지만 차이점은 있다.
    interface DelTip {
      tip: number;
    }
    interface Filter extends DelTip {
      tip: string;
      //'Filter' 인터페이스가 'DelTip' 인터페이스를 잘못 확장합니다.
      //'tip' 속성의 형식이 호환되지 않습니다.
    }
    
    type DelTip2 = { tip: number };
    type Fliter2 = DelTip2 & { tip: string }; // never타입​
     extends에서는 중첩되는 키값의 타입이 다른 경우 에러가 뜨지만, 교차타입에서는 never타입이 된다.

확장을 통해서 새로운 타입을 만들 경우, 네이밍의 의도를 명확히 표현할 수도 있고, 작성 단계에서 예상치 못한 에러도 예방할 수 있다.

 

2. 타입 좁히기(타입 가드)

타입 좁히기는 변수 또는 표현식의 타입 범위를 더 작은 범위로 좁혀나가는 과정을 말한다.

자바스크립트 연산자를 활용한 타입가드는 typeof, instanceof ,in 같은 연산자를 활용하여 제어문으로 특정 타입 값을 가질 수 밖에 없는 상황을 유도하여 좁히는 방법이 있다.

  1. 원시 타입을 추론할 떄 : typeof 연산자 활용
    string, number, boolean, undefined, object, function, bigint, symbol의 경우 typeof를 통해 타입을 좁힐 수 있다.
    const replaceHyphen: (date: string | Date) => string | Date = (date) => {
      if (typeof date === 'string') {
      //이 분기에서는 date 타입이 string이다.
        return date.replace(/-/g, '/');
      }
      return date;
    };​
  2. 인스턴스화된 객체 타입을 판별할 때: instanceof 연산자 활용
    인스턴스화된 객체 타입을 판별하는 타입 가드로 쓸 수 있다.
    const onKeyDown = (event: KeyboardEvent) => {
      if (event.target instanceof HTMLInputElement && event.key === 'Enter') {
        event.target.blur();
      }
    };​
  3. 객체의 속성이 있는지 없는지에 따른 구분  : in 연산자 활용
    in 연산자는 객체의 속성이 있는지 확인한 다음 true/false를 반환한다. 이를 통해 속성 여부에 따른 객체 타입 구분을 할 수 있다.
    type Fish = { swim: () => void };
    type Bird = { fly: () => void };
    
    function move(animal: Fish | Bird) {
      if ('swim' in animal) {
        return animal.swim();
      }
    
      return animal.fly();
    }​


  4. is 연산자로 사용자 정의 타입 가드 만들어 활용하기 (타입 명제 활용)
    반환 타입이 타입 명제인 함수를 정의하여 사용하는 방식
    type Fish = { swim: () => void };
    type Bird = { fly: () => void };
    
    //is
    function isFish(pet: Fish | Bird): pet is Fish {
      return (pet as Fish).swim !== undefined;
    }
    function getSmallPet(): Fish | Bird {
      const randomNumber = Math.random();
    
      if (randomNumber < 0.5) {
        return { swim: () => console.log('Fish is swimming') };
      } else {
        return { fly: () => console.log('Bird is flying') };
      }
    }
    
    let pet = getSmallPet();
    
    if (isFish(pet)) {
      pet.swim();
    } else {
      pet.fly();
    }​

3. 타입 좁히기(식별할 수 있는 유니온)

태그된 유니온 혹은 식별할 수 있는 유니온으로도 타입을 좁힐 수 있다.

아래에는 자바스크릡트가 덕 타이핑 언어이기 때문에 별도의 타입 에러를 발생하지 않는 wrongErrorArr이 있다.

존재하지 않는 타입이지만 에러가 생기지 않았고, 이럴 경우 무수한 에러 객체가 생겨날 위험이 생긴다.

type TextError = {
  errorCode: string;
  errorMessage: string;
};

type ToastError = {
  errorCode: string;
  errorMessage: string;
  toastShowDuration: number; //토스트 시간
};

type AlertError = {
  errorCode: string;
  errorMessage: string;
  onConfirm: () => void; // 확인 버튼 후 액션
};

type ErrorFeedbackType = TextError | ToastError | AlertError;
const errorArr: ErrorFeedbackType[] = [
  { errorCode: '100', errorMessage: '텍스트' },
  { errorCode: '200', errorMessage: '토스트', toastShowDuration: 3000 },
  { errorCode: '300', errorMessage: '앨럿', onConfirm: () => {} },
];

const wrongErrorArr: ErrorFeedbackType[] = [
  {
    errorCode: '900',
    errorMessage: '잘못된 에러',
    toastShowDuration: 3000,
    onConfirm: () => {},
  },
];

 

 

이를 해결하기 위해서 에러 타입을 구분할 필요가 있는데, 비슷한 구조를 가지지만 서로 호완되지 않도록 만들어주기 위해서는 타입들이 서로 포함 관계를 가지지 않도록 정의해야 한다. 이때 사용하는 것이 식별할 수 있는 유니온 이다.

 

식별할 수 있는 유니온 
타입간의 구조 호환을 막기 위해 타입마다 구분할 수 있는 판별자를 달아 주어 포함 관계를 제거하는 것

type TextError2 = {
  errorType: 'Text';
  errorCode: string;
  errorMessage: string;
};

type ToastError2 = {
  errorType: 'Toast';
  errorCode: string;
  errorMessage: string;
  toastShowDuration: number; //토스트 시간
};

type AlertError2 = {
  errorType: 'Alert';
  errorCode: string;
  errorMessage: string;
  onConfirm: () => void; // 확인 버튼 후 액션
};

type ErrorFeedbackType2 = TextError2 | ToastError2 | AlertError2;
const errorArr2: ErrorFeedbackType2[] = [
  { errorType: 'Text', errorCode: '100', errorMessage: '텍스트' },
  {
    errorType: 'Toast',
    errorCode: '200',
    errorMessage: '토스트',
    toastShowDuration: 3000,
  },
  {
    errorType: 'Alert',
    errorCode: '300',
    errorMessage: '앨럿',
    onConfirm: () => {},
  },
];

const wrongErrorArr2: ErrorFeedbackType2[] = [
  {
    errorType: 'Toast',
    errorCode: '900',
    errorMessage: '잘못된 에러',
    toastShowDuration: 3000,
    // onConfirm: () => {}, //개체 리터럴은 알려진 속성만 지정할 수 있으며 'ToastError2' 형식에 'onConfirm'이(가) 없습니다
  },
];

 

errorType을 통해서 구별할 수 있게 된다.

 

이때 식별할 수 있는 유니온을 선정할 때는 판별자는 유닛 타입으로 선언되어야 정상적으로 작동한다.

유닛 타입 : 오직 하나의 정확한 값을 가지는 타입 (null, undefined, 리터럴 타입,1 등등)

 

공식 깃허브에서는 2가지 조건을 건다

  • 리터럴 타입
  • 판별자로 선정한 값ㅇ에 적어도 하나 이상의 유닛 타입이 포함되어야 하며, 인스턴스화 할 수 있는 타입은 포함되면 안된다.

4. Exhaustiveness Checking 으로 정확한 타입 분기 유지하기

모든 케이스에 대해서 철저하게 타입을 검사하는 것을 말하며 타입 좁히기에 사용되는 패러다임 중 하나

Exhuastiveness Checking을 통해 모든 케이스에 대한 타입 검사를 강제할 수 있다.

const getProduction = (productPrice: ProductPrice): string => {
  if (productPrice === '10000') return '배민상품권 1만원';
  if (productPrice === '20000') return '배민상품권 2만원';
  else exhaustiveCheck(productPrice); //'string' 형식의 인수는 'never' 형식의 매개 변수에 할당될 수 없습니다.ts(2345)
  return '배민상품권';
};

const getProduction2 = (productPrice: ProductPrice): string => {
  if (productPrice === '10000') return '배민상품권 1만원';
  if (productPrice === '20000') return '배민상품권 2만원';
  if (productPrice === '50000') return '배민상품권 5만원';
  else {
    exhaustiveCheck(productPrice);
    return '배민상품권';
  }
};


const exhaustiveCheck = (param: never) => {
  throw new Error('type Error!');
};

 

 

이 부분에 대해서는 exhaustiveCheck 함수가  런타임 코드에 포함되기 때문에 의견이 분분하지만, 우형 이야기에서는 성능에 거의 영향도 없고, 테스트 코드로 해결할 수 있는 부분도 있지만 소통 측면에서 매력적이다고 하였습니다.

 

 

var getProduction2 = function (productPrice) {
    if (productPrice === '10000')
        return '배민상품권 1만원';
    if (productPrice === '20000')
        return '배민상품권 2만원';
    if (productPrice === '50000')
        return '배민상품권 5만원';
    else {
        exhaustiveCheck(productPrice);
        return '배민상품권';
    }
};
var exhaustiveCheck = function (param) {
    throw new Error('type Error!');
};

JS파일에도 exhaustiveCheck 함수가 남아 있다.

728x90

2장에선 기본적인 타입과 타입시스템에 대해서 알아봤다면, 3장에서는 자바스크립트 자료형에서 표현되지 않은 자료형에 대해서 설명한다. 존재는 했지만 동적 타이핑 특성 상  표현할 필요성이 없던 자료형들을 타입스크립트에서 정적 타이핑을 하기 위해 정의가 된 애들이다.

 

타입스크립트의 타입 계층 구조

출처 : 우아한 타입스크립트 with React 교재

 

 타입스크립트만의 독자적 타입

1. any  타입

자바스크립트에 존재하느 모든 값을 오류 없이 받을 수 있음.

  • 개발 단계에서 임시로 값을 지정해야 할 때
  • 어떤 값을 받아올지 또는 넘겨줄 지 알 수 없을 때
    ex) 외부라이브러리,웹 API요청에 따라 다양한 값을 반환하는 API, 콜백함수 등등
  • 값을 예측할 수 없을 때 암묵적으로 사용
    ex) 외부라이브러리나 웹 API요청에 따라 다양한 값을 반환하는 API

 

2.  unknown타입

아직 무엇이 할당될 지 모를 때 사용. any타입과 유사한데  비교하면 아래와 같다.

any unknown
-어떤 타입이든 ANY에 할당가능
-any 타입을 어떤 타입으로도 할당 가능(never제외)
-어떤 타입이든 unknown 타입에 할당 가능
-unknown 타입은 any타입 외에 다른 타입으로 할당 불가능

 

any 와 추가적인 차이점은 할당하는 시점에서는 에러가 발생하지 않으나, 실행시에 에러가 발생한다.

즉, 어떠한 타입이든 가져올 수 이찌만 개발자에게 엄격한 타입검사를 강제한다.

 

배민팀에서는 강제 타입 캐스팅을 할 때 쓰거나, 에러가 발생한다는 점 덕에 any보다 선호한다고 했다.

 

3.  void 타입

함수에서 반환 결과가 없는 경우를 말한다.

자바스크립트에서는 반환하는 값이 없는 함수의 경우 undefined 가 반환되는데, 타입스크립트에서 void는 undefined는 아니다.

void에는 null과 undefined가 할당가능하지만 ,strictNullChecks를 세팅할 경우 에러가 뜨고 추천되지 않는 방식이다.

 

4.  never 타입

함수와 관련해서 쓰는데, 값을 반환할 수 없는 경우 ( 반환 결과가 없는 것과 다름)

크게 2가지 경우로 나뉘는데

  • 에러를 던지는 경우
  • 무한히 함수가 실행되는 경우

5.  Array 타입

자바스크립트에서도 Object.prototype.toString.call(..) 연사자로 확인할 수 있는데 이 함수는 객체의 인스턴스까지 알려주기 때문에 나오는 것이다.

var arr = [];
console.log(Object.prototype.toString.call(arr)); //[object Array]

자바스크립트에서 존재하는 타입이라 생각할 수 있는데 구분하는 이유는 아래와 같다.

 

  • 자바스크립트에서는 배열을 객체에 속하는 타입으로 분류함
  • 타입스크립트에서 Array라는 타입을 사용하기 위해서는 타입스크립트의 특수한 문법을 다뤄야 함

 

타입 제한하기

 

자바스크립트에서는 배열안에 들어갈 값에 제한이 없다.( 다양한 값이 들어갈 수 있음)

하지만 이러한 성격은 정적 타이핑과 잘 부합하지 않고, 실제로 다른 언어는 배열의 원소로 하나의 타입만 허용한다.

이를 제한하기 위해서 자료형+ [] 형식으로 배열 타입을 선언할 수 있고, 여러가지 선언할 경우 유니온 타입을 사용할 수 있다.

const arr = [];
console.log(Object.prototype.toString.call(arr));

const array: Array<number> = [1, 2, 3];
const array2: number[] = [1, 2, 3];

const unionArr: Array<number | string> = [1, 'string'];
const unionArr2: (number | string)[] = [1, 'string'];

 

길이 제한하기

자바등 다른 정적 타입의 경우 길이도 제한하는 경우가 많다. 이때 사용하는 것이 튜플 타입이다.

대괄호 안에 들어가는 원수의 수가 튜플이 가질 수 있는 원소의 개수다

const tuple: [number] = [1];
const tuple2: [number, string] = [1, 'string'];

 

가장 대표적인 예시는 react의 useState의 경우이다.  

const [username, setUsername] = useState('');

 

이렇게 튜플 타입을 사용할 경우 객체의 구조 분해 할당을 사용하는 것과 다르게 변수 이름에서 자유로워진다.

유연성이 향상된다.

 

6  enum 타입

열거형이라고도 부르는 특수한 타입이다. 

일일이 값을 할당할 수도 있지만  ,누락된 번호의 경우에는 이전 멤버 값의 숫자를 1씩 늘리며 자동 할당한다.

enum Language {
  Typescript, //0
  Javascript, //1
  Java, //2
  Python = 'Python',
  Kotlin = 300,
  Rust, //301
}

 

열거형은 그 자체로 변수 타입으로 지정할 수 있다. 따라서 코드 가독성을 높인다.

 

enum ItemStatusType {
  HOLd = 'HOLD', //배송보류
  READY = 'READY', //배송 준비
  DELIVERING = 'DELIVERING', // 배송 중
  DELIVERED = 'DELIVERED', // 배송완료
}

const checkItemAvailable = (itemStatus: ItemStatusType) => {
  switch (itemStatus) {
    case ItemStatusType.HOLd:
    case ItemStatusType.READY:
    case ItemStatusType.DELIVERING:
      return false;
    case ItemStatusType.DELIVERED:
      defalut: return true;
  }
};

 

위의 예시와 같이 얻을 수 있는 효과로는

  • 타입 안정성 (명시되지 않은 값은 받을 수 없다)
  • 명확한 의미 전달과 높은 응집력 : 타입이 어떤 값을 다루는 지 명확하고, 아이템 상태에 대한 값을 모아 놓아서 응집력이 높다
  • 가독성 : 응집도가 높기 떄문에 말하고자 하는 바가 명확하다.

 

다만 열거형의 경우에는 범위를 넘어서는 경우 접근해도 에러가 생기지 않는다. const enum으로 열거형 선언을 할 경우 역방향으로의 접근을 허용하지는 않지만, 문자열로 사용하는 것이 더 안전하다.

(교제에서는 에러는 발생하지 않는다고 하는데, 에러가 생김,,)

const enum NUMBER {
  ONE = 1,
  TWO = 2,
}

enum NUM {
  ONE = 1,
  TWO = 2,
  THREE,
}

const notMyNumber: NUM = 100; //'100' 형식은 'NUMBER' 형식에 할당할 수 없습니다.ts(2322)

const myNumber: NUMBER = 100; //'100' 형식은 'NUMBER' 형식에 할당할 수 없습니다.ts(2322)

const enum STRING_NUMBER {
  ONE = 'ONE',
  TWO = 'TWO',
}

const myStringNum: STRING_NUMBER = 'THREE'; //'"THREE"' 형식은 'STRING_NUMBER' 형식에 할당할 수 없습니다.ts(2322)

 

또한 추가적인 문제점은 열거형은 타입스크립트 코드가 자바스크립트 코드로 변환되어 트리쉐이킹 과정에서 사용하지 않는 코드로 인식하지 못하는 경우가 발생할 수 있다.

(function (NUM) {
    NUM[NUM["ONE"] = 1] = "ONE";
    NUM[NUM["TWO"] = 2] = "TWO";
    NUM[NUM["THREE"] = 3] = "THREE";
})(NUM || (NUM = {}));
var notMyNumber = 100; //'100' 형식은 'NUMBER' 형식에 할당할 수 없습니다.ts(2322)
var myNumber = 100; //'100' 형식은 'NUMBER' 형식에 할당할 수 없습니다.ts(2322)
var myStringNum = 'THREE'; //'"THREE"' 형식은 'STRING_NUMBER' 형식에 할당할 수 없습니다.ts(2322)

 

 타입 조합

 

1.  교차 타입

여러 가지 타입을 결합하여 하나의 단일 타입을 만들 수 있다

A & B는 A와 B 모두를 만족하는 타입이다.  

type ProductItem = {
  id: number;
  name: string;
  type: string;
  price: number;
};

type ProductItemWithDiscount = ProductItem & { discountAmount: number };

const item: ProductItem = { id: 1, name: '굽네', type: '치킨', price: 20000 };

const disItem: ProductItemWithDiscount = {
  id: 1,
  name: '굽네',
  type: '치킨',
  price: 20000,
  discountAmount: 500,
};

 

2.  유니온 타입

A와 B중 하나를 만족하는 타입. 그렇기 때문에 둘 중 하나만 해당하는 경우에는 컴파일 에러가 뜬다.

type CardItem = {
  id: number;
  name: string;
  type: string;
  imageUrl: string;
};

type PromotionEventItem = ProductItem | CardItem;

const printPromotionItem = (item: PromotionEventItem) => {
  console.log(item.name);
  //   console.log(item.imageUrl);// 컴파일에러
};

 

3.  인덱스 시그니처

특정 타입의 속성 이름은 알 수 없지만 속성값의 타입을 알고 있을 때 사용하는 문법

[Key:K ] :T 꼴로 타입을 명시해주면 된다.

타입을 추가로 명시할 수 있는데 이 경우에는 인덱스 시그니처에 포함되어야 한다.

interface IndexSignatureEx2 {
  [key: string]: number | boolean;
  length: number;
  isValid: boolean;
  // name: string;//에러
}

 

4.  인덱시드 엑세스 타입

다른 타입의 특정 속성이 가지는 타입을 조회하기 위해 사용된다.

type Example = {
  a: number;
  b: string;
  c: boolean;
};

type IndexedAccess = Example['a'];
type IndexedAccess2 = Example['a' | 'b'];
type IndexedAccess3 = Example[keyof Example];

const indexedConst: IndexedAccess = 3;
const indexedConst2: IndexedAccess2 = 3;
const indexedConst2n1: IndexedAccess2 = '3';
const indexedKeyOf1: IndexedAccess3 = '3';
const indexedKeyOf2: IndexedAccess3 = 3;
const indexedKeyOf3: IndexedAccess3 = false;

 

배열의 타입을 조회할 때도 쓸 수 있다.

 

const PromotionList = [
  { type: 'product', name: 'chicken' },
  { type: 'product', name: 'pizza' },
  { type: 'card', name: 'cheer-up' },
];

const PromotionList2 = [
  { type: 'product', name: 'chicken' },
  { type: 'product', name: 'pizza' },
  { type: 3, name: 'cheer-up' },
];

type ElementOf = (typeof PromotionList)[number];
const promotionData: ElementOf = { type: 'product', name: 'chicken' };
type ElementOf2 = (typeof PromotionList2)[number]; //type ElementOf2 = {  type: string; name: string;} | { type: number; name: string;}

5.  맵드 타입

map은 보통 유사한 형태를 가진 여러 항목의 목록 A를 변환된 항목의 목록 B로 바꾸는 것을 의미한다.

type MapExample = {
  a: number;
  b: string;
  c: boolean;
};

type Subset<T> = {
  [K in keyof T]?: T[K];
};


const aExample: Subset<MapExample> = { a: 3 };
const aExample2: Subset<MapExample> = { b: 'string' };
const aExample3: Subset<MapExample> = { a: 3, b: 'string' };

a,b,c가 필수 이던 타입을 아래와 같이 optional 하게 바꿀 수도 있고,

-readonly  -? 를 통해서 readonly 와 옵셔널을 제거할 수도 있다. 불필요한 반복을 줄이기 위해 사용한다.

 

type ReadOnlyEx = {
  readonly a: number;
  readonly b: string;
};

type CreateMuatable<Type> = {
  -readonly [Property in keyof Type]: Type[Property];
};

type ResultType = CreateMuatable<ReadOnlyEx>; //{ a: number; b: string;}

 

6.  템플릿 리터럴 타입

템플릿 리터럴 문자열을 사용하여 문자열 리터럴 타입을 선언할 수 있는 문법

type Stage = 'init' | 'select-image';
type StageName = `${Stage}-stage`; //"init-stage" | "select-image-stage"

 

새로운 유니온 타입을 만들 수 있다.

 

7.  제네릭

정적 언어에서 다양한 타입간의 재사용을 높이기 위해 사용하는 문법

제네릭안에는 어떠한 타입이든 올 수 있지만, 생성 시점에 원하는 타입을 특정할 수 있다.

아래 예시는 string만 올 수 있는 배열을 제네릭 문법을 통해 생성하였다.

type ExampleArrayType<T> = T[];
const array1: ExampleArrayType<string> = ['피자', '치킨'];

 

타입  추론이 가능한 경우에는 타입 명시를 생략할 수 있다.

function exampleFunc<T>(arg: T): T[] {
  return new Array(3).fill(arg);
}

function exampleFunc2<T>(arg: T): number {
  return arg.length; // 에러가 생김 'T' 형식에 'length' 속성이 없습니다
}

 

이때 어떤 타입이든 올 수 있기에 특정 타입에만 존재하는 메서드를 제네릭을 사용할 떄 함꼐 쓰면 안된다.

또한 화살표함수에서 JSX문법에서의 꺽쇠괄호와 혼동이 생길 수 있으므로 해당 경우에는 extends 키워드를 사용하여야 한다.

const arrowExampleFunc2 = <T extends {}>(arg: T): T[] => {
  return new Array(3).fill(arg);
};

 

 

제네릭의 사용처는 다양하다

  1. 함수의 제네릭
    함수의 매개변수나 반환값에 다양한 타입을 넣고 싶을 때
  2.  호출 시그니처의 제네릭
    호출 시그니처란 타입스크립트의 함수 타입 문법으로 함수의 매개변수와 반환 타입을 미리 선언하는 것을 말한다.
    // 배민선물하기 예시
    interface useSelectPaginationProps<T> {
      categoryAtom: RecoilState<number>;
      filterAtom: RecoilState<string[]>;
      sortAtom: RecoilState<SortType>;
      fetcherFunc: (
        props: CommonListRequest
      ) => Promis<DefaultResponse<ContentListResponse<T>>>;
    }​
  3. 제네릭 클래스
    외부에서 입력된 타입을 클래스 내부에 적용할 수 있는 클래스
    클래스 이름 뒤에 타입 매개변수인 <T>를 선언해준다.
    class GenericNumber<NumType> {
      zeroValue: NumType;
      add: (x: NumType, y: NumType) => NumType;
    }
     
    let myGenericNumber = new GenericNumber<number>();
    myGenericNumber.zeroValue = 0;
    myGenericNumber.add = function (x, y) {
      return x + y;
    };​


  4. 제한된 제네릭
    타입 매개변수에 대한 제약 조건을 설정하는 기능을 말한다.
    유니온 타입을 상속해서 유연성을 잃지 않으면서 타입 제약을 할 수 있다. 
    아래에는 extends로 제한하여서 length 메서드가 가능하게 하였다.
    interface Lengthwise {
      length: number;
    }
    
    function loggingIdentity<Type extends Lengthwise | string>(arg: Type): Type {
      console.log(arg.length);
      return arg;
    }​
     

 

제네릭 사용 유의사항
제네릭의 장점을 다양한 타입을 받을 수 있어 코드를 효율적으로  재사용할 수 있다는 점이다. 그러다보니 API 응답 값의 타입을 지정할 때 제네릭을 많이 쓰게 된다.

다만 유의할 점은 3가지다

  • 제네릭을 굳이 사용하지 않아도 되는 타입
    type GType<T> = T;​
  • any 사용하기
    마찬가지로 제네릭과 any를 사용한다면 자바스크립트를 쓰는 것과차이가 없다.
  • 가독성을 고려하지 않기
    너무 남용할 경우 남이 알아보기 힘들다.

 

 

 

 

728x90

들어가며 

단순히 타입스크립트 뿐만 아니라 다른 언어의 타입은 어떻게 작용하는 지 그리고 타입스크립트는 어떤 특징을 가지고 있는지 설명해주면서 알려준다. 

 한 언어에 능통하게 되면, 추후 다른 언어를 배울 때 쉽다고 한다. 이 책을 보면서 왜 그런지에 대해서 느끼게 되었다.

또한 enum이라던가 type과 interface등 어떠한 상황에 쓰는지에 대한 배민팀에 대한 고민도 같이 읽을 수 있어서 좋았다.

 

목차는 1.  타입, 2.  타입시스템, 3. 원시 타입 4. 객체 타입 총 4가지다.

 

1. 타입

책에서는 타입을 크게 5가지 관점에서 다룬다. 

  • 자료형으로서의 타입
  • 집합으로서의 타입
  • 정적과 동적 타입
  • 강타입과 약 타입
  • 컴파일 방식

자료형으로서의 타입

타입스크립트는 자바스크립트의 슈퍼셋언어이다. 따라서 자바스크립트와 굉장히 유사한 관계가 있다.

따라서 타입스크립트의 자료형으로서의 타입 또한 자바스크립트와 같은데, 자료형의 타입이 필요한 이유는 메모리를 효율적으로 쓰기 위해서다. (데이터의 종류에 따라 필요한 메모리 공간이 다르므로)

흔히 백준에서 알고리즘 문제를 풀다보면, 자바스크립트가 데이터 타입이 조금 더 빡빡한 c나 자바보다 메모리 부족 문제를 쉽게 겪는다.

 

자바스크립트는 ECMAScript 표준으로 7가지 데이터 타입을 갖는다.

  • undefined
  • null
  • Boolean
  • String
  • Symbol
  • Numeric
  • Object

집합으로서의 타입

타입스크립트에서는 값이 가질 수 있는 유효한 범위의 집합을 제한해준다.

 

정적 타입과 동적 타입

자바스크립트는 동적타입이고 타입스크립트는 정적타입이다.

정적이냐 동적이냐의 구분은 변수의 타입이 언제 정해지냐에 따라서 결정되는데,

 

  • 동적 타입 : 런타임 
  • 정적 타입 : 컴파일

이러한 동적 타입 시스템은 개발 과정에서는 에러가 없지만 프로그램이 실행 단계에서 에러 혹은 예상치 못한 결과가 생길 수 있다.

 

강타입과 약타입

암묵적 타입 변환 (런타임에 타입이 자동으로 변경되는지) 에 따라서 강타입과 약타입을 구분한다.

책에서는 -를 예시로 설명해주는데 C++ , 자바, 파이썬 , 루비, 자바스크립트, 타입스크립트를 분류하면 아래와 같다.

강타입 약타입
파이썬, 루비, 타입스크립트 C++,자바,자바스크립트

 

약타입의 언어 또한 동적 타입처럼 런타임에 예상치 못한 에러를 발생시킬 수 있다.

 

컴파일 방식

컴파일이란 보편적으로 컴퓨터가 이해할 수 있는 기계어로 바꿔주는 과정이다.

이떄 타입스크립트는 컴파일의 결과물이 예외적으로 자바스크립트 파일이다. 

이유는 자바스크립트의 런타임 에러를 컴파일 단계에서 잡기 위한 목적으로 생성 되었기 때문이다.

 

요약하자면 타입스크립트

undefined, null, Boolen, Numeric, Object, String, Symbol의 자료형 타입을 가진 강타입의 동적 타입이다.

타입스크립트는 집합으로서의 의미를 가지며 컴파일 시 자바스크립트 파일이 생긴다.

 

2. 타입 시스템

타입스크립트의 특징은 5가지가  있다.

 

1. 타입 에너테이션 방식

let isDone: boolean = false

변수 이름뒤에 :type 구문을 붙여 데이터 타입을 명시해준다. 점진적 특징을 가져서 선언부 제거해도 되나, 타입스크립트의 타입시스템이 타입을 추론하는 과정에서 어려움을 겪는다.

(타입이 자바스크립트와 다르게 Boolean이 아닌 boolean이다)

 

2. 구조적 타이핑

 

타입을 사용하는 언어중에서는 타입이 컴파일 이후에도 이름으로 남는 경우가 있다.

이를 구체화한 타입 시스템이라고 하는데,  타입스크립트는 이와 다르다.

 

타입스크립트는 구조로 타입을 구분한다.

interface Developer {
	faceValue : number;
}

interface BankNote {
	faceValue : number;
}

let developer: Developer = {faceValue :52};
let bankNote: BankNote = {faceValue :52};

developer = bankNote;
bankNote = developer;

 

구체화한 타입 시스템에서는 developer 와 bankNote의 타입 이름이 다르니깐 서로 할당할 수 없어야 한다.

하지만 구조적 타입 시스템이기에 타입스크립트에서는 마지막 2줄이 가능하다.

 

구조적 서브 타이핑

구조적 서브타이핑이란 객체가 가지고 있는 속성을 바탕으로 타입을 구분하는 것이다. 

 

interface Developer {
	faceValue : number;
}

interface FrontendDev {
	faceValue : number;
    major : String
}

let developer: Developer;
let frontendDev: FrontendDev = {faceValue :52, major: "React" };

developer = frontendDev;

 

developr 과 frontendDev는 다른타입이지만 faceValue를 공통적으로 가져서  할당할 수 있다.

이는 함수의 매개변수에도 적용이 된다. 또한 상속 역시 구조적 타이핑을 기반으로 하고 있다.

class Person {
	name : string;
    age : number;
    
    constructor(name:string, age:number){
     this.name = name;
     this.age = age;
    }
}

class Developer {
	name : string;
    age : number;
    sleepTime : number;
    constructor(name:string, age:number, sleepTime : number){
     this.name = name;
     this.age = age;
     this.sleepTime = sleepTime;
    }
}    

function greet(p:Person {
    	console.log(`Hello, I'm'${p.name}`);
}

const developer = new Developer("zig",20,7);

gree(developer);

 

타입 A가 타입 B의 서브 타입이라면 A 타입의 인스턴스는 B타입이 필요한 어디든 위치할 수 있다.

 

구조적 타이핑인 이유

 

타입스크립트가 구조적 타이핑을 채택한 이유는 자바스크립트를  모델링한 언어이기 때문이다.

자바스크립트는 덕 타이핑 기반으로 어떤 함수의 매개변수값이 올바르게 주어진다면 그 값이 어떻게 만들어졌는지 신경쓰지 않는다. 하지만 덕 타이핑도 속성을 바탕으로 타입을 검사하지만, 런타임에 체크를 한다.

하지만 구조적 타이핑은 컴파일 타이밍에 타입을 검사한다.

 

구조적 타이핑의 결과

interface Cube {
  width: number;
  heigth: number;
  depth: number;
}

function addLines(c: Cube) {
  let total = 0;

  for (const axis of Object.keys(c)) {
    const length = c[axis];
    total += length;
  }
}

const namedCube = {
  width: 6,
  heigth: 5,
  depth: 4,
  name: 'SweetCube',
};

addLines(namedCube);

 

구조적 타이핑이기에 아래와 같은 경우엔 에러가 생긴다. Cube의 타입이 당연히 number일 거라 생각했지만 name은 string 타입이다. 이러한 한계를 극복하고자 유니온이라는 방법이 생긴다.

 

3. 점진적 타입 확인

점진적 타입 검사란 컴파일 타임에 타입을 검사하면서 필요에 따라 타입 선언 생략을 허용하는 방식이다.

타입 선언을 생략하면 동적 검사를 수행하고 암시적 타입 변환이일어난다.

function add(x,y){
	return x+y;
}

// 암시적 타입 변환 결과
function add(x:any,y:any):any;

 

 자바스크립트의 슈퍼셋 언어이기에 자바스크립트의모든 코드는 타입스크립트 코드로 봐도 무방하다.

따라서 .ts 파일에 자바스크립트 문법을 소스코드를 작성해도 문제가 생기지 않고, 이때 점진적 타이핑이라는 특징이 유효하게 사용가능하다. 다만 정적 타입의 정확성을 100%보장하지는 않는다.

 

const names = ["zig,colin"];

console.log(names[2].toUpperCase());
//TypeError names[2]는 undefined라서 toUpperCase가 불가능하다.

 

4. 값 vs 타입

타입스크립트는 컴피알이 되면 자바스크립트로 바뀌게 된다.

이때 컴파일이 되면 사라지는 단순히 타입만 알려주는 것과, 실제로 사용되는 값(메모리에 저장하는 데이터)이 구분되어야 한다.

이때 변수의 이름과 type, interface 키워로 설정한 커스텀 타입의 이름은 서로 충돌하지 않는다. (타입은 별도의 네임스페이스에 저장된 후 런타임에는 제거되기 떄문)

 

다만 타입스크립트에서 값과 타입의 구분은 맥락에 따라 달라지기에 주의할 필요는 있다.

예시로는 구조분해 할당을 들 수 있는데 구조분해 할당할 경우 3번같이 사용하게 되면 key Value로 해석하고 오류가 생긴다.

// 일반적인 타입
function email(options : {person:Person; subject:string;body:string}){
  //...
}

// 자바스크립트에서 구조분해 할당할 경우
function email({person, subject,body}){
  //...
}

// 타입스크립트에서  잘못된 경우
// key Value로 해석될 여지가 있음
function email({person:Person; subject:string;body:string}){
  //...

}

// 제대로 사용
function email({person, subject,body}):{person:Person; subject:string;body:string}{
  //...
}

 

또한 enum과 Class의 경우에 값과 타입이 둘 다 존재해서 주의해야 한다.(타입이 런타임에 실제 값으로도 사용될 수 있다.)

 

enum이 타입으로 쓰이는 경우

enum WeekDays {
  MON = 'Mon',
  TUES = 'Tues',
  WEDNES = 'Wednes',
  THURS = 'Thurs',
  FRI = 'Fri',
}

type WeekDaysKey = keyof typeof WeekDays;
function printDay(key: WeekDaysKey, message: string) {
  const day = WeekDays[key];
  if (day <= WeekDays.WEDNES) {
    console.log(`It's still ${day}, ${message}`);
  }
}

printDay('TUES', 'wanna go home');

 

값으로 쓰이는 경우

enum MyColors {
  BLUE = '#0000FF',
  YELLOW = '#FFFF00',
}

function whatBlueColor(palette: { BLUE: string }) {
  return palette.BLUE;
}

whatBlueColor(MyColors);

 

심볼이 값으로 사용된다는 것은 타입스크립트 컴파일 이후에도 자바스크립트에서 해당 정보가 남아있다는 뜻이다.

 

각각 키워드를 값과 타입으로 분류하면 아래와 같다.

키워드 타입
class Y Y
enum Y Y
const, let ,var Y N
function Y N
namespace Y N
type N Y
interface N Y

 

5. 타입 확인하는 방법

typeof 연산자를 통해서 7가지 기본 데이터 타입과 (Boolean, null, undefined, Number,BigInt, String,Symbol) 과 Function, 호스트 객체 , 오브젝트 객체 를 얻을 수 있다.

이때 typeof 연산자는 자바스크립트에서도 존재하며 값에 쓰일 때와 타입에 쓰일 때가 다르다.

값 : javascript의 런타임의 typeof 연산자가 된다.

타입 : 타입스크립트의 typeof가 된다.

class의 경우에는 값으로 사용할 때는 결국 함수이므로 function 타입인데, 타입으로 쓰이는 경우는 생성자 함수를 뱉는다.

 

instanceof 연산자를 통해서 타입이 보장된 상태에서 안전하게 값의 타입을 정제하여 사용할 수 있다.

 

as 키워드를 사용하는 타입 단언을 통해서, 강제 형 변환과 유사한 기능으로 타입을 지정할 수 있다. (개발자가 해당 값의 타입을 더 잘 파악할 수 있는 경우.. 대신 런타임에서는 효력이 없다.)

 

타입가드를 통해 타입을 보장할 수도 있다.

 

3. 원시 타입

자바스크립트의 슈퍼셋이기 때문에, null과 undefined를 제외한 모든 원시값은 해당 원시값을 래핑한 객체를 가지며 이름이 파스칼 표기법으로 쓰지 않는다.

  1. boolean
    Truthy와 Falsy를 제외한 true, false.
  2. undefined
    아직 할당되지 않음
  3. null
    undefined와 마찬가지로 존재하지 않음을 알리는 값인데 없다를 강조할 떄 null을 쓴다.(undefined는 아직 없는 것일 수도 있어서)
  4. number
    자바와 달리 숫자를 number로만 사용하고, NaN과 Infinity도 포함
  5. bigInt
    2**53-1 를 넘어가는 수를 처리할 수 있는데, number와 상호 작용이 되지 않는 서로 다른 타입이다.
  6. string
    문자열 (공백도 포함함)
  7. symbol
    ES2015에 도입된 타입으로 어떤 값과도 중복되지 않는 유일한 값

4. 객체 타입

  1. object
    원시 타입이 아닌 모든 값이 해당되어 사용하는 것이 any 와 마찬가지로 추천되진 않는다.
  2. {}
    객체 리터럴 방식으로 객체를 생성할 때 쓰인다. 타입스크릅테엇는 객체를 타이핑할 떄도 중괄호를 쓸 수 있는데, 중괄호 안에 객체의 속성 타입을 지정해주는 방식으로 쓰인다.
    const noticePopup: { title: string; description: string } = {
      title: 'IE 지원 종료',
      description: '2022.07부로 종료',
      //startAt : "2022.07.15" // 지정  타입에 없을므로 오류 생김
    };​


  3. array
    number [] 와 같이 쓰이는 방법과 Array 키워드로 쓰는 방법이 있다.
    튜플 타입도 대괄호라 사용한다는 점을 주의해야함
  4. type과 interface 키워드
    타입스크립트에서만 독자적으로 사용할 수 있는 키워드로 객체를 타이핑하기 위해 쓰임.
    type NoticePopupType ={
      { title: string; description: string }
     }
    
    const noticePopup:NoticePopupType = {
      title: 'IE 지원 종료',
      description: '2022.07부로 종료',
    };​​
  5. function
    자바스크립트에서 함수도 일종의 객체로 간주하지만 typeof를 쓸 경우 별도의 function 타입이 존재한다.
    이때 객체 타이핑과 다르게 주의해야할 점이 2가지가 있다.
    1. typeof 연산자로 확인한 function 키워드 자체를 type으로 쓰지 않음
    2. 함수는 매개 변수 목록을 받을 수 있는데, 타입스크립트에서는 매개변수도 별도 타입을 지정해야 함
    function add (a:number,b:number):number{
    	return a+b;
    }​
    위 예시는 함수를 작성할 떄 type을 쓰는 법이고, 함수 자체 타입은 호출 시그니처를 정의하는 방식을 사용하여 지정할 수 있다.
    type add = (a:number, b:number) =>number; // 화살표 함수 방식으로만 정의

 

 

 

728x90

읽게 된 이유

정적 타입테스트를 위해서 Typescript를 도입했었다. 이때 단순히 extends 이외의 다른 문법에 대해선 크게 사용하지 않았다. Omit 등 여러 기능이 있는건 알았지만 익숙치 않아서 사용하지 않았다. 그러다 보니 Type이 굉장히 많아지거나 가독성이 좋지 않다고 느꼈다. 이러한 시점에서 Fastcampus의 강의를 들었는데, Omit이나 여러 문법을 쓰니 내가 쓰는 타입보다 훨씬 우아하게 느껴졌다. 단순히 타입스크립트를 아는 것 보다 실무에서 어떻게 쓰는지에 대한 호기심을 가지게 되었고, 이 책을 알게 되어 읽게 되었다.

 

1장은 타입스크립트가 왜 필요한가에 대한 내용이다. 제목 그대로 들어가며!! 크게 3가지로 분류할 수 있을 거 같다.

 

1. 웹사이트의 발전

단순히 정적이던 웹사이트가 발전하게 되었다. 정보를 보여주기만 했던 사이트가 유저와의 상호작용을 하는 사이트가 되고, ajax이후엔 새로고침없이도 내용이 동적으로 바뀌게 되었다. 이러면서 정적이던 웹사이트는 동적인 웹 어플리케이션으로 진화하게 된다.

이러한 웹 어플리케이션이 되면서 점점 다양한 기기와 다양한 데이터를 다루게 되었고 ,다루는 데이터 또한 방대해졌다.

이런 상황에 맞물려 컴퍼넌트 베이스 개발(CBD) 방법론이 등장하게 되었다. 컴퍼넌트는 모듈과 유사하게 하나의 독립된 기능을 재사용하기 위한 코드 묶음이다. 다른 컴포넌트와의 의존성을 최소화하거나 없애야하고 이는 흔히들 말하는 '관심사의 분리'일 것이다. 이런 식으로 웹이 발전하고 동적 웹 서비스의 수요 증가가 이어지면서 자연스럽게 자바스크립트 개발자의 증가로 이어졌다. 또한 결과물이 증가하면서 개발자의 협업이 점점 필요하게 되었다.

 

2. 협업에서의 자바스크립트의 한계

 코딩을 시작하고 3개월 쯤 지났을 무렵 나는 '동적'이다를 굉장히 긍정적인 단어로 받아들이고 있었다. 자율성이 높고,,, 하지만 자유롭다는 것, 동적인다는 것은 특징일 뿐이며 당연히 단점이 존재할 수 밖에 없다.

 

 동적 타입 언어

자바스크립트는 동적 타입언어라서 런타임에 해당 변숫값이 할당될 때 해당 값의 타입에 따라 변수 타입이 결정된다. 그렇기 때문에 사용자가 실수하기가 쉽다.

책에서 드는 예시는 덧셈이다.

const sumNumber= (a,b)=>{
	return a+b;
    };
    
sumNumber(1,2);//3
sumNumber(100);//NaN
sumNumber("a","b")//ab

 

우린 숫자의 덧셈용으로 해당 함수를 만들었는데, 사용하는 다른 동료는 다르게 사용할 수 있다. 변수의 개수, 변수의 타입등이 일치하지 않을 수 있다.  이러한 특징 때문에 우리는 런타임에 예상치 못한 에러를 겪게 되고 이는 예상치 못한 에러를 유발할 수 있다.

 

이러한 방법을 해결하기 위한 3가지 대안으로

JSDoc, propTypes, 다트등 3가지 방법이 나왔는데, 자바스크립트 스스로가 인터페이스를 기술할 수 있는 언어로 발전해야하는 목소리가 커졌고, 그 결과가 타입스크립트다.

 

3. 협업을 위한 타입스크립트

자바스크립트 코드를 그대로 사용할 수 있는 점(슈퍼셋 언어이다) 자바스크립트에 점진적으로 적용 가능하다는 장점과 아래 3가지 장점에 타입스크립트는 많은 환영을 받았다.

 

3.1 안정성 보장

정적 타이핑을 제공해서, 컴파일 단계에 타입 검사를 해준다. 이에 의해 자바스크립트의 타입 에러를 줄일 수 있고, 런타엠 에러를 사전에 방지할 수 있어 안정성이 향상된다.

 

3.2 개발 생산성 향상

자바스크립트를 쓰다가 타입스크립트를 쓸 떄 나도 많이 느꼈던 편리합니다. VsCode와 Typescript 모두 마이크로소프트에서 만들었따. 이에 의해 호완이 굉장히 잘되고, 이를 바탕으로 자동 완성 기능이 잘 제공된다.

 

3.3 협업에 유리

타입 스크립트에서 인터페이스, 제네릭을 지원하는데 인터페이스가 기술되면 코드 이해력이 향상된다. 또한 복잡할 앱일수록 협업하는 개발자 수도 증가하는데, 자동 완성 기능이난 기술된 인터페이스는 코드 이해를 더욱 쉽게 해준다.

 

 

 

 

 

 

+ Recent posts