All Articles

정적언어인 TypeScript에 대해 알아보자 1탄-정적언어와 타입선언

이번 포스팅부터 맥북으로 작성하였습니다. 😜 <- 맥북의 상징 이모티콘


image.png

Ts하면 생각나는건.. 왜 타슈밖에 없죠..? 왜 타슈의 이니셜 같죠…? 타슈가 뭔지 모르는 분들을 위해 살짝 알려드리자면…

image.png (이미지 출처 : 타슈 홈페이지) 대전시에서 운영하는 자전거서비스 이름입니다 ㅋㅋㅋㅋ 여튼… 타슈의 Ts 말고 우리는 TypeScript의 Ts를 알아볼겁니다! 룰루~

그 전에 정적언어와 동적언어에 대해 먼저 알아보고 갑시다!

정적언어와 동적언어

동적언어

정적언어와 동적언어를 언제 써야 하는지에 대한 구분은 바로 “Type”을 언제 결정하느냐에 따라 다른데요. 우리에게 넘나 친숙한 자바스크립트는 동적언어입니다. 그말인즉슨, 타입을 미리 결정하지 않고 변수를 먼저 선언해도 되는것!! 변수를 선언한 이후에 타입을 맘대로 지지고 볶고 해도 에러가 나지 않습니다. 이렇게…!

image.png

동적언어의 장점

  • 타입고민없이 걍 써도 되서 배우기 쉬움
  • 코드의 양이 적을때 생산성 굿굿

동적언어의 단점

  • 타입 오류가 런타임시 발견쓰

정적언어

오늘 소개해드릴 타입스크립트가 바로 정적언어 중에 하나입니다. 자바스크립트와는 달리 타입을 미리 결정한 후에 변수를 선언해야 합니다. 예를 들어…

image.png

아까와 동일한 코드를 TypeScript에 입력해보면 이러한 타입 에러가 발생합니다. 이런식으로 자동으로 타입을 인식하는 기능을 타입추론(type inference)라고 한다고 하네용! 그렇담 저 코드에 에러가 발생하지 않게 하려면 어떻게 해야할까요! 미리 타입을 선언해줘야 하겠죠?

image.png

짠! 미리 변수의 타입을 숫자 혹은 string 이라고 지정해주니 무서운 빨간줄은 안 생겼어요. Ts에서 변수를 선언할 때는 위와 같이 변수 이름과 콜론, 타입을 써주면 됩니다.

정적언어의 장점

  • 코드의 양이 많을 때 생산성 굿굿
  • 타입 오류가 컴파일시 발견쓰

정적언어의 단점

  • 변수 선언할때마다 타입 고민쓰… 진입장벽 높음 ㅠㅠ

타입스크립트의 여러 가지 타입

image.png

아까 위에서 간략하게 설명하긴 했지만..!
Ts에서 타입을 선언할 때는 변수 이름과 콜론, 타입을 선언해주어야 합니다.

자, 이제 타입스크립트에서 타입을 다루는 방식에 대해 알아보자能!

number, boolead, string

const size: number = 123;
const isBig: boolean = size >= 123;
const msg: string = isBig ? 'big' : 'small';

console.log(size); // 123
console.log(isBig); // true
console.log(msg); // big

number면 number, boolean이면 boolean, string이면 string이라고 선언한다.
그 와중에 msg 선언하는 거 넘나 신박… 삼항연산자를 이용해서 저렇게 해줄수도 있군..! 😎

숫자형 배열 및 혼합형 배열

// 숫자형 배열
const arr1: number[] = [1, 2, 3];
const arr2: Array<number> = [2, 4, 6];
arr1.push(4)
arr2.push(8)
arr2.push('ten') // Error!
console.log(arr1) // [1,2,3,4]
console.log(arr2) // [2,4,6,8]

// 혼합형 배열
const size: number = 123;
const isBig: boolean = size >= 123;
const msg: string = isBig ? 'big' : 'small';
const data : [string, number] = [msg, size];
console.log(data); // ['big', 123]
console.log(data[0].substr(1)); // ig
console.log(data[1].substr(1)); // Error!

숫자형 배열의 타입은 number[] 라고 선언해주거나 Array<number>라고 선언한다. number와 string이 혼합된 배열은 선언할 때 [string, number]라고 선언해준다. data라는 배열을 선언했는데, data의 1번째 값의 타입은 숫자이므로 거기서 substr을 쓸 수 없으니까 Error 발생!!

여튼 저런 혼합형 배열을 tuple이라고도 한다!

null과 undefined

let v1: undefined = undefined;
let v2: null = null;
v1 = 123;

let v3: number | undefined = undefined;
v3 = 123;

undefined는 undefined로, null은 null로 선언해준다. JS와의 차이점을 살펴보자면..
(이 아래의 캡쳐화면은 JS에서의 화면입니다)

image.png

  • JS에선 null의 type이 object였는데 TS에선 null의 type은 null이다!
  • JS에선 초기에 null이나 undefined여도 변수에 새로운 값을 할당하면 변수에 그 값이 대입이 되지만, TS에선 Type Error가 발생한다…!

문자열 리터럴과 숫자 리터럴 타입

let num: 10 | 20 | 30;
num = 10;
// num = 15; 숫자 10, 20, 30만 가질수 있는 타입으로 정의된 것이라 type error!

let job: 'frontend' | 'backend';
// job = 'designer' 마찬가지로 type error!

숫자 리터럴 타입을 정의할 때, number가 아니라 직접적인 숫자를 대입하면 해당하는 숫자만 가지도록 정의가 되는 것이라
변수에 다른 숫자를 할당하면 Type Error가 발생하게 된다!
문자열 리터럴도 마찬가지다. 선언했을 때 당시의 문자가 아니라면 Type Error 발생!

Any

말 그대로 Any! so cool하게 어떤 타입의 값이든 허용하는 타입니다.

let value: any;
value = 123;
value = '456';
value = () => {};

실제로 타입을 알 수 없거나 타입 정의가 되지 않은 외부 패키지(자바스크립트라던지.. javascript라던지..)를 사용할때 쓰면 된다. 그치만 남발하지 않는게 좋다. 우리가 TS를 쓰는 이유가 없어지니깐 ㅠㅠ

void와 never

void는 아무값도 반환하지 않고 종료되는 함수에,
never은 예외 발생으로 비정상적으로 종료되거나 무한루프 때문에 종료되지 않는 함수에 씀
이건 깊이 들어가봐야 알겠지만.. 왜 never같은 값을 선언해주는지 아직은 모르겠다 😂
애초에 무한루프 안 걸리게 로직을 짜야하는거 아닌가 ? ㅠㅠ 모르겠다…

여튼.. 예시를 함 보자!

function f1(): void {
    console.log('hungry');
}

function f2(): never {
    throw new Error('some error');
}

function infiniteLoop(): never {
    while (true) {
    }
}

대충 어떤건지 알겠는데 저걸 굳이 왜 써주는지는 아직도 의문쓰..!
이건 담주 목욜에 있을 위코드 프론트앤드 스터디 이후 더 자세히 알게되지 않을까..😱

object

예시를 먼저 보자!

let obj: object;
obj = { name: 'Doori' };
console.log(obj.name) // 세상에 타입에러 발생...

obj라는 변수는 object라고 선언해주었지만, 그 안에 들어가는 값은 정의를 해주지 않아서 Type Error 발생… 진짜 너무하네 일반적으로 객체에서 속성정보를 포함해서 타입을 정의하려면 인터페이스를 이용하는데.. 이건 글이 길어지므로 다음 이 시간에! 🤓

교차타입과 유니온타입

설명은 코드를 먼저 보시오..!

let numnum: (1 | 3 | 5) & (3 | 5 | 7);
numnum = 3;
numnum = 1;

교차타입은 & 기호로, 유니온타입은 |으로 표시한다.
위에서 이미 타입을 문자열 혹은 숫자로 정의하고 싶을때 유니온타입을 쓴 적이 있다.
근데 numnum이 1 또는 3또는 5 이고 3 또는 5또는 7인데
3과 5가 겹치니까 3 또는 5가 아니면 타입에러가 난다… 당황쓰..
그러면 1과 7은 억울해서 어떻게 사나…

이것에 대한 의문도 다음주 목요일에 있을 위코드 프론트앤드 스터디 때 풀 예정!🥺

열거형 타입

열거형 타입의 원소는 값으로 사용될 수도, 타입으로 사용될 수도 있음.

enum Fruit {
  Apple, Strawberry=2, Kiwi
}

console.log(Fruit.Apple, Fruit.Strawberry, Fruit.Kiwi); // 0 2 3

열거형 타입에서 첫 번째 값에 할당을 해주지 않으면 무조건 0이 할당됨.
각각의 값들엔 string이나 number의 값을 줄 수 있는데, 할당되지 않은 값에는 이전 값에서 +1 된 만큼의 값이 할당된다!
위의 예시에서 딸기가 2여서 그 옆의 키위는 자동으로 3이 되었다.

아래의 예는 열거형 타입의 객체의 속성을 예시로 들어봤다!

enum Fruit {
  Apple, Strawberry=2, Kiwi
}

console.log(Fruit.Strawberry); // 2
console.log(Fruit['Strawberry']); // 2
console.log(Fruit[2]); // Strawberry

세번째 콘솔은 좀 신박하다 ㅋㅋ
원소의 이름과 값이 양방향으로 매핑되어 있어서 값에 할당된 숫자를 입력해도 해당하는 값이 불러와진다 오오~

근데 열거형 타입의 값이 문자열일때는 세번째 방식이 통하지 않는다.

enum Numb {
  One='first', Two='second', Three='third'
}
console.log(Numb.One); // first
console.log(Numb['One']); // first
console.log(Numb['first']) // Error!

열거형 타입을 자주 쓰는 경우 몇 가지 유틸리티 함수를 만들어 쓰는게 좋다고 한다!

  • 열거형 타입의 원소의 개수를 알려주는 함수
function getEnumLength(enumObject: any) {
  const keys = Object.keys(enumObject);
  return keys.reduce(
    (acc,key)=> (typeof enumObject[key] === 'string' ? acc+1 : acc),0
  );
}
  • 열거형 타입에 존재하는 값인지 검사하는 함수
function isValidEnumValue(enumObject: any, value: never | string) {
  if (typeof value === 'number') {
    return !!enumObject[value];
  }
  else {
    return (
      Object.keys(enumObject)
      .filter(key => isNaN(Number(key)))
      .find(key => enumObject[key] === value) != null
    );
  }
}

자 그럼 이걸 어떻게 적용하냐가 중요한데! 아래의 예시를 같이 보시죠..!

enum Fruit {
  Apple,
  Banana,
  Orange
}

enum Language {
  Korean = 'ko',
  English = 'en',
  Chinese = 'ch'
}

console.log(getEnumLength(Fruit), getEnumLength(Language)); // 3 3

위의 함수에서 첫번째 식을 적용해보면 길이가 3으로 잘 나온다. 근데 두번째 식은 내가 뭔가 잘 못 쓴건지.. 자꾸 에러가 나서 이건 다음 기회에 알아보도록 하자!

원래는 해당하는 object에 그 값이 있는지 확인할 수 있는 식이다. Language안에 Jp가 있으면 false가 나오고.. 이런 식인데 왜 에러가 날까~ 😂

상수 열거형

const 로 선언하는 enum을 상수 열거형이라고 하는데 여기까진 괜찮다.

const enum Fruit {
  Apple, Banana, Orange
}

const fruit: Fruit = Fruit.Apple;

const enum Language {
  Korean = 'ko',
  English = 'en',
  Chinese = 'ch'
}

const lang: Language = Language.Korean;

console.log(fruit) // 0
console.log(lang) // 'ko'

약간의 특이점이 있다면.. const를 쓰면 객체형을 쓸 수 없다는것..? 아까 객체의 길이를 세주었던 함수를 다시 써보자

function getEnumLength(enumObject: any) {
  const keys = Object.keys(enumObject);
  return keys.reduce(
    (acc,key)=> (typeof enumObject[key] === 'string' ? acc+1 : acc),0
  );
}

const enum Fruit {
  Apple, Banana, Orange
}
console.log(getEnumLength(Fruit))	// Error!

아까였으면 3이 나왔을 값이 에러가 발생한다.

image.png

const는 변수선언할 때 말고 enum을 써야할 땐 웬만해선 쓰지말자!

함수 타입

함수타입을 정의하려면 매개변수 타입과 반환타입이 있는데..

function getInfoTxt(name: string, age: number) : string { // 매개변수 타입
  const nameTxt = name.substr(0, 10);
  const ageTxt = age >= 35 ? 'senior' : 'junior';
  return `name : ${nameTxt}, age: ${ageTxt}`
}

const v1: string = getInfoTxt('mike', 23);  // 반환타입
console.log(v1) // name : mike, age: junior
const v2: string = getInfoTxt('mike', '23'); // Error!

코드의 첫줄에 있는 것처럼 parameter로 들어가는 값에 타입을 정의 한게 바로 매개변수 타입 그리고 함수의 리턴값이 대입되는 변수에 타입을 정의한게 반환타입!

v2에서의 에러는 매개변수 타입이 age가 number라고 정해져있지만 string이 들어가서 탈락!

선택 매개변수

말그대로 써주는 게 필수가 아니라 선택인 매개변수를 말한다.

function getInfoTxt(name: string, age: number, language?: string) : string {
  const nameTxt = name.substr(0, 10);
  const ageTxt = age >= 35 ? 'senior' : 'junior';
  const languageTxt= language ? language.substr(0,10): '';
  return `name : ${nameTxt}, age: ${ageTxt}, language: ${languageTxt}`
}

getInfoTxt('mike', 23, 'ko');
getInfoTxt('mike', 23); // language는 생략가능쓰 
getInfoTxt('mike', 23, 123); // Error!

첫번째 줄에 language가 string이라고 지정되기 전 ? 가 쓰인다. 이렇게 물음표 콜론, 그 다음 타입이 나올 경우가 선택 매개변수!

두번째 콘솔처럼 선택매개변수는 생략이 가능쓰 But, 써 줬는데 타입이 매칭되지 않으면 에러가 난다!!

추가로, 선택매개변수는 꼭 필수 매개변수의 오른쪽에 와야 하는데

function getInfoTxt(name: string, language?: string, age: number) : string {.....}

이렇게 쓰면 탈락! 그럼 어떻게 써줘야하냐..

function getInfoTxt {
	name: string,
    language: string | undefined,
    age: number,
}: string {....}

선택매개변수가 필수 매개변수 중간에 껴있을 경우 꼭 undefined를 유니온타입으로 써줘야 한다!

매개변수의 기본값 정하기

function getInfoTxt(
    name: string, 
    age: number = 28,
    language = 'korean'
  ) : string {
  // 생략.....
}

console.log(getInfoTxt('mike')); 
console.log(getInfoTxt('mike', 23)); 
console.log(getInfoTxt('mike', 23, 'en'));

여기서 콘솔에 찍은 값 세 가지 모두 에러가 나지 않는다. 기본값이 있는 매개변수들은 모두 선택매개변수라고 했다. 즉, name만 필수란 말씀.

여기서 의문이 드는것은.. 문자열 리터럴과 숫자 리터럴 변수 선언을 할 땐 해당하는 숫자나 문자열이 아닐경우 에러가 났는데.. 이건 변수 선언이 아니라 함수형이라 예외인건가?

이것에 대한 궁금증도 목요일 스터디에서 풀릴 예정!

나머지 매개변수

function getInfoTxt(name: string, ...rest: string[]): string {
//....
}

말그대로 rest를 써서 타입을 정의할 수 있다는 것 같은데 책에는 예시가 부실하다 ㅋㅋ 나중에 차차 알아갈 예정쓰!

this타입 쓸 차롄데 글이 너무 길어지기도 하고, 재미없어 지는것 같아서 다음 포스팅에 계속!

Reference