Typescript

타입스크립트를 공부하게된 계기

800x0

조직 개편 이후 AI 플랫폼을 개발하는 팀에서 계정을 만드는 IAM 팀으로 이동하게 되었다.

팀 이동이라고 하면 나쁜 이유로 이동했을 것 같지만 생각보다 합리적인 이유로 팀 이동이 되었고 나 또한 그것을 받아들이게 되었다.

인턴이 끝나고 정규직 전환 이후, 나는 팀에 급한 업무를 지원하는 일종의 소방수 역할을 수행하게 되었다.

처음으로 지원을 간 곳은 카카오 워크였고, 그 이후는 현재 소속된 IAM 팀이었다.

IAM은 계정 체계를 구축하는 팀이었는데, 내 소속은 AI 플랫폼을 개발하는 팀에 소속되어있지만 업무는 IAM 업무를 하는 상당히 요상한 모양새가 지속되고 있었다.

이러한 상태가 지속되자 슬슬 주위 팀원들도 내가 당연히 IAM 팀원이 될 것 (이미 IAM 팀원이 아니냐고 하시는 분들도 있었다!)이라 예상하던 중 조직 개편이 시작되었고 당연하게도 나는 이전, 현 조직장과 면담 이후 IAM 팀으로 이동하게 되었다.

IAM 팀에 와서 놀랐던 부분은 기술 스택이었는데, 스프링 부트를 쓰지 않는 것과 (여기에는 Security를 커스터마이징 해야 했기 때문이라는 나름의 합리적인 이유가 있었다.) Java를 당시 최신 버전인 14버전을 쓴다는 것, 그리고 Typescript와 nest.js(node 기반)의 서비스를 운영중이라는 사실이었다.

기술 스택이 굉장히 합리적으로 결정되어있는 부분도 마음에 들었고 은근 안정적인 선에서 최신 트렌드를 쫓아가는 리더 쉘든의 의견이 듬뿍 반영된 점도 좋았다. (나 또한 최신 기술 좋아한다. 물론 검증된 선에서만)

또한 인턴 때는 Go를 잠깐 사용했는데, 팀 이동 이후 자바 + 스프링을 주로 사용하고 개발할 것들이 아주 아주 아주 아주 많아서 너무 좋았다. (정말이다.)

특히 Typescript는 예전부터 배워보고 싶었는데 이번 기회에 써볼 수 있어서 무척 설렜다.

또한 nest.js의 모양새와 typeorm이 스프링의 그것들과 너무나 비슷했고 (DI, JPA와 놀랍도록 너무나도 닮아있었다.)

그러다보니 1시간 정도 기존 코드 훑어보니 nest.js로 서비스 코드를 작성할 수 있게 되었다. (이것도 nest.js + ts의 장점인 듯)

Kotlin을 공부했기 때문에 사용을 하는 것에는 크게 무리가 없었지만 (자바스크립트와 Kotlin, 자바 그 어느 사이 쯤 되는 언어) 당연히 깊이 있게 이해하고 쓰는 느낌은 아니라서 책 한권은 정독해야할 필요성을 느껴 공부를 시작했다.

현재 사이드 프로젝트로 진행하고 있는 프로젝트도 React + TS로 개발중이었기에 더욱이 공부해둬야할 필요성을 느꼈다.

본 글은 내가 책을 읽으며 잘 모르고 생소한 부분만 위주로 정리했다.

이미 다 알고 있는 변수 선언이나 타입과 같이 쉬운 부분들은 건너뛰었다.

Typescript란

타입 스크립트는 우리에게는 윈도우 OS와 파워포인트를 개발한 회사로 유명한 마이크로소프트에서 개발한 언어이다.

마이크로소프트의 최근 행보를 보면 깃허브를 인수한다던지, VSCode를 개발한다던지 하는 개발 생태계에 적극적으로 뛰어드는 모습을 많이 볼 수 있는데, TS도 그러한 산출물 중 하나이다.

기존 자바스크립트의 단점을 보완하고 현대에 개발된 대다수 언어들이 정적 타입 언어를 채택하면서 자바스크립트의 동적 타이핑이 문제가 되는 경우가 많았고, 이것을 보완하기 위해 등장했다.

즉, 타입이 없던 자바스크립트에 타입이 추가된 언어라고 생각하면 이해가 빠를 듯 하다.

TS는 우리가 주로 ES라고 부르는 자바스크립트 ECMA의 모든 버전을 포함하는 언어이다. (이를 ES6 이후를 모두 포함해서 ES NEXT라고 부른다고 한다.)

또한 트랜스파일 (요즘은 그냥 컴파일이라고 부른다더라)을 통해 컴파일 시점에 타입 에러를 잡아내고 빌드 시에 JS로 변환되어지는 방식을 사용하고 있다.

덕분에 기존에 JS에서 협업이 어려웠던 부분을 TS로 많이 완화시켰으며 해외에서는 TS 개발자의 몸 값이 높다고도 한다.

실제로 JS와 TS를 둘 다 써본 입장에서 당연히 개발하기야 JS가 편하지만, 협업하기에는 TS만한 언어가 없다고 생각될 정도로 타입이 있고 없고 차이가 꽤 크다는 것을 느꼈다.

또한 TS를 개발한 개발자가 C# 개발자라 그런지 다형성을 지원하기 위한 인터페이스나, 타입 추론, 제네릭 타입 등을 지원하면서 현대 프로그래밍 언어가 유행처럼 가지고 가는 기능들을 많이 갖게 되었다. (Go도 내년이면 제네릭이 나오겠지)

null과 undefined

타입스크립트팀에서는 더이상 null과 undefined를 구분하지 않는다고 한다.

그리고 null을 사용하지 않고 undefined를 사용할 것을 강조한다고 한다.

그러나 nodeJS에서만큼은 예외적으로 == null을 사용하는 것을 표준으로 허용한다고 한다.

이는 이미 개발된 대부분의 nodeJS 라이브러리가 ==null을 사용하고 있기 떄문으로 보인다.

콜백 함수

아래와 같이 매개변수로 동작하는 함수를 콜백함수라고 부른다

const f = (callback: () => void): void => callback();

이는 프레임워크의 API 구현에 유용한 기능을 제공해준다.

고차 함수와 클로저, 부분 함수

고차 함수의 정의는 또 다른 함수를 반환하는 함수를 의미한다.

고차 함수의 매우 일반적 형태는 다음과 같다.

const add1 = (a: number, b: number): number => a + b; // 일반 함수
const add2 = (a: number): ((number) => number) => (b: number): number => a + b; // 고차 함수

아래의 add 함수는 add2 고차함수의 이름만 바꾼 함수이다.

const add = (a: number): ((number) => number) => (b: number): number => a + b;
const result = add(1)(2);
console.log(result); // 3

어떻게 이런 구문이 가능할까?

조금 더 쉽게 아래의 구현을 보자.

type NumberToNumberFunc = (number) => number;

이제 NumberToNumberFunc 타입의 함수를 반환하는 add와 같은 함수를 만들어보자.

type NumberToNumberFunc = (number) => number;
export const add = (a: number): NumberToNumberFunc => {
  // NumberToNumberFunc 타입의 함수 반환
};

이를 중첩 함수로 구현 가능하다.

type NumberToNumberFunc = (number) => number;
export const add = (a: number): NumberToNumberFunc => {
  const _add: NumberToNumberFunc = (b: number): number => {
    // number 타입의 값 반환
  };
  return _add;
};

고차 함수는 이처럼 중첩 함수를 반환할 수 있다.

최종적으로 _add의 몸통을 구현하면 다음처럼 add라는 이름의 고차 함수가 완성된다.

type NumberToNumberFunc = (number) => number;
export const add = (a: number): NumberToNumberFunc => {
  const _add: NumberToNumberFunc = (b: number): number => {
    return a + b; // 클로저
  };
  return _add;
};

여기서 클로저로 표시된 부분을 주목해보자.

a는 add 함수의 매개변수요, b는 _add 함수의 매개변수다.

즉 _add의 관점에서 보면 a는 외부에 선언된 변수이다.

함수형 프로그래밍 언어에서 위와 같은 형태를 클로저라고 부른다.

고차 함수는 이 클로저 기능이 반드시 필요하다.

최종적으로 우리는 다음과 같은 코드를 작성할 수 있게 된다.

import { NumberToNumberFunc, add } from './add';
let fn: NumberToNumberFunc = add(1);

fn에 담긴 NumberToNumberFunc는 함수 표현식이므로 다음과 같이 fn 뒤에 함수 호출 연산자를 붙일 수 있다.

import { NumberToNumberFunc, add } from './add';
let fn: NumberToNumberFunc = add(1);

let result = fn(2);
console.log(result); // 3
console.log(add(1)(2)); // 3

여기서 우리는 fn 자체는 add(1)을 저장하는 일종의 임시 변수의 역할만을 수행한다는 것을 알 수 있다.

fn(2)가 호출 되어야 비로소 연산을 수행하기 때문에 우리는 이것을 부분 애플리케이션 혹은 부분 적용 함수라고 부른다.

Reference

Do it Typescript, 저자 전예횽님



© 2022. by minkuk

Powered by minkuk