TypeScript와 함께하는 Fullstack Development

QueryPie 개발기 #7: TypeScript is Everywhere

Woo Gim
QueryPie
15 min readFeb 18, 2019

--

몇 년 전만해도 TypeScript는 JavaScript 내에서 부분적으로 사용되며 ‘어떻게 혹은 왜 써야하는지’ 에 대한 논의의 대상이 되곤 했습니다. 하지만 개발 환경은 점점 빠르게 변하고 이제는 ‘TypeScript를 왜 쓰지 않는지?’의 경지가 되었기 때문에 서두에서 그 중요성을 따로 언급하지는 않겠습니다.

What is TypeScript and why would I use it in place of JavaScript? [closed] (출처: Stackoverflow)

이번 글에서는 제가 TypeScript를 사용해서 개발한 프로젝트들(QueryPie 및 QueryPie Protocol 등)에서 TypeScript를 어떻게 전방위적으로 활용하여 Fullstack으로 개발하고 있는지에 대해 알려드리고자 합니다. 시작에 앞서 이 글은 분량의 한계로 하나하나의 방법을 자세히 설명하지 못하고 있다는 점을 말씀드립니다.

그보다는 TypeScript를 이렇게도 폭넓게 사용하는구나 하는 정도로 가볍게 읽어주시면 좋겠습니다. 제 글을 읽어주셔서 미리 감사드립니다.

React로 만든 SPA에서 TypeScript 사용

가장 일반적인 TypeScript의 사용 패턴이 아닐까 합니다. SPA 환경은 대부분 번들링을 위해 Webpack, Parcel 등의 모듈 번들러와 babel등의 트랜스코더를 포함하고 있기 때문에 TypeScript를 적용하기에 아주 쉽습니다.

특히 React는 TypeScript와의 궁합이 좋습니다. 다만 Props의 타입선언에서 몇가지 팁이 필요한데 이 내용은 단락의 끝 부분에서 다루겠습니다.

CRA에서 사용하기

Create React App (https://facebook.github.io/create-react-app/)

React 프로젝트를 Create React App (CRA) 도구를 이용해서 생성하는 것이 익숙하신 분들은 그 경험 그대로 프로젝트를 생성하실 때 --typescript 플래그를 붙이면 됩니다.

$ npx create-react-app my-app --typescript

이것은 CRA 2.1.0 이상 부터 공식적으로 지원되며, 자세한 내용은 여기에서 확인하실 수 있습니다.

NEXT.js에서 사용하기

Next.js (https://nextjs.org/)

검색엔진 최적화(Search Engine Optimization, SEO) 등이 필요해서 Server-side Rendering (SSR)을 고려하는 경우 간단하게 NEXT.js를 사용하는 경우가 많습니다. NEXT.js는 파일 경로 기반 라우팅이 장점이자 단점인 도구로 다양한 편의 기능으로 높은 인기를 누리고 있죠. NEXT.js는 TypeScript를 공식적으로 지원하며 아래의 플러그인을 필요로 합니다.

npm install --save @zeit/next-typescript

NEXT.js로 프로젝트를 생성하는 방법과 이후의 설정은 다소 복잡하여 여기를 따라주시기 바랍니다.

React Props Type 선언하기

React에서 Props의 Type은 Interface나 Type으로 선언하고, React.Component 구문에 제네릭(Generic)으로 지정해줍니다.

interface IProps {}class Info extends React.Component<IProps> {

이 때 Redux나 MobX 등의 상태관리 도구를 Higher-Order Component (HOC)로 사용하는 경우 등을 포함해서 Props에 다른 요소(property)가 주입되는 경우가 빈번하게 발생하죠. 그래서 Props 타입 선언이 점점 복잡해지고, 이 때 주입되는 요소(property)는 컴포넌트를 사용하는 부모 프로세스에서 온 것이 아니기 때문에 Props 타입선언이 Optional로 가득차게 됩니다.

이러한 문제는 TypeScript의 Omit과 Partial을 사용하여 외부로 노출되는 Props를 정확하게 정의해주면 해결됩니다.

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;interface InjectedProps {appStore: IAppStore;}interface IProps extends InjectedProps {object: IObjectPanel;}type ExposedProps = Omit<IProps, keyof InjectedProps> & Partial<InjectedProps>;@inject('appStore')class Info extends React.Component<IProps> { ... }export default Info as React.ComponentType<ExposedProps>;

최종적으로 Info라는 React 컴포넌트를 React.ComponentType<ExposedProps> 이라는 형태로 노출시켜주는 코드입니다. 다소 복잡하지만 이렇게 함으로써 정확하게 부모 컴포넌트로부터 받아야 할 Props를 지정해줄 수 있습니다. 이러한 TypeScript의 Generic과 Type 합성을 이해하게 되시면 TypeScript를 자유자재로 쓸 수 있습니다.

복잡한 데이터를 다루는 SPA에서 TypeScript 사용

복잡한 데이터를 다루는 복잡한 앱에서 TypeScript의 진가가 드러납니다.

strictNullChecks 사용하기

견고한 TypeScript 코드를 위해서는 tsconfig.json에서 아래 옵션을 켜주는게 좋습니다.

// tsconfig.json{"compilerOptions": {"strictNullChecks": true,}}

이 옵션을 켜주면 TypeScript 컴파일러가 null이거나 undefined가 될 수 있는 모든 코드에 에러를 표시해 줍니다. 이를 통해 1차적으로 복잡한 데이터를 다룰 때 발생할 수 있는 가장 흔한 에러인 TypeError를 방지할 수 있습니다. JS에서는 Java의 NullPointerException (java.lang.NullPointerException)와 비견 될 만큼 무서운 에러지요.

TypeError: null or undefined has no properties

TypeScript에는 다양한 strict 컴파일러 옵션이 있습니다. 다른 strict 옵션에 비해 strictNullChecks는 꼭 사용하시기를 권해드립니다. TypeScript의 자세한 컴파일러 옵션은 아래를 참고해주세요.

Compiler Options (https://www.typescriptlang.org/docs/handbook/compiler-options.html)

모델 주도 (Model Driven) 상태 관리

복잡한 데이터를 다루는 경우 상태 관리가 대단히 까다롭게 됩니다. 위에서 예고한 바와 같이 이럴 때 TypeScript가 유용하게 쓰이는데요, SPA에서 사용하는 대표적인 상태 관리 도구로는 Redux가 있죠. 그렇지만 Redux가 가지는 복잡하고 비대한 Action 코드로 인해 MobX라는 도구도 대안으로써 인기를 끌고 있습니다.

Redux와 MobX, 그리고 MobX-state-tree

그리고 하나 더, 복잡한 데이터를 다루기 위해서 모델 기반의 견고한 상태관리를 가능하게 하는 MobX-state-tree라는 도구가 있습니다.

MobX-state-tree는 TypeScript로 작성되어 모델의 완벽한 타입 선언을 제공합니다. 또한 선언된 모델에 대해서 완벽한 타입 추론을 통해서 데이터를 다루는 코드의 결함을 획기적으로 줄여 줍니다.

MobX-state-tree를 통해 정의된 User라는 모델
MobX-state-tree를 통해 정의된 me라는 객체의 User 모델 타입 추론

SPA에서 사용하는 상태관리 도구에 대한 좀 더 자세한 비교기는 제가 전에 작성한 슬라이드를 참고해주시기 바랍니다.

Node.js에서 TypeScript 사용

React에서 TypeScript를 사용하는 것이 유명하고 편리한 도구들로 인해 다소 수월하였다면 Node.js에서 TypeScript를 사용하는 방법은 많이 알려져 있지 않은 것 같습니다. 하지만 의외로 Node.js에서 TypeScript를 사용하는 것은 간단합니다.

TSC 사용하기

TypeScript를 Node.js 프로젝트에 포함하고, TypeScript로 작성된 ts파일은 TypeScript Compiler (TSC)를 통해 일반 JavaScript 파일로 컴파일됩니다. 그러면 기존에 사용하던대로 Node.js를 통해서 실행하면 됩니다.

$ npm install -g typescript @types/node$ tsc app.ts    // compile$ node app.js    // run!

널리 쓰이는 Node.js 재시작 도구인 Nodemon과 조합하여 코드가 변경되면 컴파일 후에 앱을 다시 구동하는 방식으로 개발할 수도 있습니다.

$ tsc --watch app.ts$ nodemon app.js

TS-Node + Nodemon 사용하기

하지만 매 번 컴파일하는 과정이 불편하고 임시 저장으로 js 파일이 생기는 것도 찜찜하기 때문에 개발 단계에서는 TS-Node의 사용을 권해드립니다. ts-node의 자세한 정보는 여기를 참고하세요.

$ nodemon --exec 'node -r ts-node/register' app.ts

Nodemon의 exec 파라미터를 통해서 Node.js를 구동하면 Node.js에 적용되는 다양한 파라미터 등과 Node.js 구동 전에 동작시킬 다양한 명령을 포함할 수 있어서 활용도가 매우 높습니다.

다만 TS-Node는 매 번 TypeScript 컴파일을 하기 때문에 프로덕션에서는 TSC를 통해서 JavaScript 파일로 컴파일한 것을 사용하시기를 권장드립니다.

sequelize-typescript로 누리는 타이핑된 ORM

Node.js에서 사용하는 ORM(Object-relational mapping) 도구 중에는 Sequelize가 가장 유명합니다. Sequelize를 사용할 때 가장 큰 단점은 모델 정의가 어렵고 모델이 자동 완성과 타입 체크가 되지 않는다는 점입니다.

Sequelize (http://docs.sequelizejs.com/)

Sequelize-typescript를 사용하면 친숙한 Class 문법으로 모델을 정의할 수 있고, 완벽한 자동완성과 타입체크가 됩니다. Sequelize-typescript의 자세한 정보는 여기를 참고하세요.

sequelize-typescript의 모델 정의는 매우 직관적. JPA(Java Persistent API)를 상당히 닮아 있다.

AWS Lambda에서 TypeScript 사용

최근 몇 년간 클라우드 컴퓨팅 시장에서 주목받은 키워드 중 서버리스(Serverless)라는 개념이 있습니다. 물리 혹은 논리적인 서버처럼 구분되고 관리가 필요한 대상이 아니라, 매 번 필요에 의해 특정 코드를 바로 동작시키고 사라지는 인스턴트 실행 환경만을 의미하는 개념인데요. 이런 서비리스 서비스 중에서 가장 유명한 것은 AWS의 Lambda입니다.

AWS Lambda 사이트의 Lambda 소개 문구 (https://aws.amazon.com/ko/lambda/)

Lambda가 제공하는 Node.js의 버전은 8.10과 6.10이 있습니다. 기본적으로는 Node.js에서 TypeScript를 사용하는 것과 동일하게 작성된 코드를 실행할 수 있습니다. 여기에 더해서 Lambda 테스트와 배포를 쉽게 하기 위한 오픈소스로 Serverless FrameworkSAM(AWS Serverless Application Model)이 있습니다.

이 중에서 간단하게 사용이 가능한 것은 SAM인데 SAM CLI를 이용해서 실행할 수 있습니다. 설치 방법은 여기를 참고하세요.

Lambda + Webpack + TS-loader 사용하기

Lambda는 소스코드 용량의 50Mb 제한이 있기 때문에 webpack으로 번들링하는 것이 유리합니다. 이 때 webpack의 ts-loader를 이용해서 TypeScript를 사용합니다.

이렇게 webpack 명령을 이용해서 번들링된 결과를 SAM CLI로 배포하면 개발과 관리가 수월합니다.

Electron에서 TypeScript 사용

Electron으로 개발할 때도 TypeScript를 사용할 수 있습니다. Electron은 TypeScript를 완벽하게 지원합니다. Electron을 사용할 때는 위의 Lambda로 번들링한 것과 유사하게 webpack을 이용해서 번들링을 하여 사용하는 것이 유리합니다.

차이점은 Electron의 Main Process(Node.js)에서 구동되는 소스코드 영역과 Browser Window(Web)에서 구동되는 소스코드를 분리하여 작성하고 번들링 해야한다는 점입니다. webpack에서 entry 부분을 각각 달리한 2개의 webpack.config.js를 이용하세요.

Electron은 완벽한 Type Definition을 제공하여 API 문서를 찾아보는 수고를 상당 부분 덜어줍니다.

Electron의 Render Process에서 구동되는 브라우저 기반의 소스코드는 앞서 소개한 것과 같이 React를 TypeScript로 작성하는 것이 효율적입니다.

Electron에서 Main Process(Node.js)와 Browser Window(Web)들 간의 데이터 통신은 기본적으로 Electron이 내장하고 있는 IPC (Inter Process Communication)를 사용하는 것이 권장됩니다. 하지만 이 IPC는 단방향 통신이고, 이벤트 방식으로 동작하기 때문에 로직을 구성하고 제어하기가 까다롭습니다. 그래서 이 IPC 통신을 기반으로 기능을 확장한 오픈소스인 electron-common-ipc를 활용하면 훨씬 더 간편하고 효율적입니다.

gRPC와 함께 TypeScript 사용

gRPC(google Remote Procedure Call)는 구글이 내부 서비스들의 통신과 제어를 위해 개발한 오픈소스 입니다. MSA(Micro Service Architecture)와 같은 복잡하고 다층적인 구조에서 각각의 독립체(Entity)들 간에 통신 수단으로 사용하기에 적합합니다. gRPC에 대한 자세한 내용은 여기를 참고해주세요.

이러한 gRPC를 사용할 때 Node.js 환경에서는 TypeScript를 사용하면 그 편의성이 극대화 됩니다. rxjs-grpc라는 오픈소스를 사용하면 gRPC의 통신 스펙에 해당하는 ‘proto 파일’을 TypeScript의 인터페이스(Interface)로 변환해주고 자동으로 gRPC 서비스를 생성해 주어서 그 사용이 매우 편리합니다.

proto 파일로 부터 변환된 인터페이스(Interface)
rxjs를 통한 쉬운 데이터 처리와 함께 Typesafe하게 gRPC를 사용할 수 있습니다.

결론: JavaScript가 쓰이는 곳이라면 모두 쓸 수 있다.

지금까지 React부터 gRPC까지 TypeScript가 어떻게 다양하게 쓰일 수 있는지다루어보았습니다. 이렇게 끝날 듯 끝나지 않는 긴 글을 쓴 이유 중 하나는 TypeScript가 JavaScript의 상위호환(Superset)이기 때문에 JavaScript가 쓰이는 모든 곳에 쓸 수 있는 특성을 가지고 있기 때문입니다. 특히 React로 프론트엔드를 개발하고, Node.js로 백엔드를 개발한다면 그 효과가 극대화됩니다. TypeScript로 프론트엔드와 백엔드 코드는 타입 정의를 공유할 수 있고 매우 견고하고 효율적인 개발이 가능합니다.

사실 저는 이제 TypeScript 없이는 JavaScript를 코딩하기 어렵습니다. 변수 이름 하나하나, 메서드의 파라미터 하나하나까지 오타가 없도록 작성해야하는 너무나도 원시적이고 비효율적인 JavaScript 개발은 더 이상 하고 싶지 않습니다.

현재 제가 CHEQUER에서 개발하고 있는 QueryPie는 앞서 소개드린 내용처럼TypeScript에 기반한 Electron, React, Node.js, gRPC 를 사용하여 견고하면서도 효율적인 개발을 하고 있습니다. QueryPie의 기술적 기반에 대해 더 자세히 알고 싶으신 분은 여기를 참고해 주세요.

끝으로 TypeScript를 사용하지 않아 고통 받고 계신 JavaScript 개발자가 아직도 계시다면 어서 사용하시고 평온한 안식을 얻으시길 빕니다.

📑영어(English Version)- https://medium.com/p/4b7af8b7d28f/

--

--