ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [TypeScript] TypeScript로 ReactJS 작성하기
    프로그래밍/웹 2018. 7. 4. 16:11
    타입스크립트와 리액트

    ReactJS와 TypeScript 같이 사용하기

    TypeScript 홈페이지 참조

    ts, typescript, 타입스크립트, Typescript: TypeScript를 칭함

    React, react, 리액트: ReactJS를 칭함

    본 문서에서 간단하게 TypeScript를 사용하는 React 프로젝를 설정하고 기본적인 테스트를 완료하는 방법을 제시한다. 본 문서를 통한 설정에는 아래의 항목들을 포함한다.

    • 리액트와 타입스크립트를 사용하는 프로젝트 생성
    • TSLint를 사용하는 코드 린팅
    • JestEnzyme을 활용한 테스팅
    • Redux를 이용한 리액트 스테이트(state) 관리

    빠른 프로젝트 생성을 위해서 create-react-app 툴을 사용한다.

    리액트 기본Node.jsnpm 을 이미 알고 있다는 전제로 설명을 진행한다.

     

    create-react-app 설치하기

    create-react-app을 사용할 경우 매우 빠르고 쉽게 리액트의 기본 설정으로 플호젝트를 세팅 할 수 있다. create-react-app은 단순히 새로운 프로젝트를 생성하는 툴이다.

     

    새로운 프로젝트 생성하기

    본 문서에서 사용하는 프로젝트의 이름은 my-app으로 한다.

    react-scripts-ts는 create-react-app을 통해 생성되는 프로젝트의 빌드 파이프라인에 타입스크립트 컴파일 과정을 추가하는 역할을 담당한다.

    위의 명령어를 실행한뒤 my-app 폴더로 들어갈 경우 폴더 구조는 아래와 같다.

    각 폴더 및 파일들을 다음의 역할을 담당한다.

    • tsconfig.json 은 타입스크립트에 특화된 옵션들을 정의한다.
    • tslint.json 은 TSLint를 위한 세팅 정보들을 포함한다.
    • package.json 은 프로젝트에서 사용하는 패키지들에 대한 의존성을 관리하며, 프로젝트 작업 중에 사용되는 명령어들에 대한 숏컷/바로가기 정보들을 제공한다. 테스팅, 프리뷰 및 배포를 위한 명령어들도 포함한다.
    • public 폴더는 배포할 HTML 등의 파일과 이미지 파일들을 포함한다. index.html 파일과는 별개로 어떤 파일이든 삭제해도 무관하다.
    • src 폴더는 자바스크립, 타입스크립트 및 CSS 코드를 포함한다. index.tsx 가 최초 엔트리 파일이며, 이름을 바꾸지 않길 권장한다.

     

    프로젝트 실행하기

    프로젝트를 실행하고 확인해보는 방법은 매우 간단하다.

    위의 명령어는 package.json에 정의된 start 스크립트를 실행한다. 이를 통해서 기본적인 코드가 저장된 페이지를 로딩하는 서버를 실행시킬 수 있다. 기본적으로 http://localhost:3000 을 통해 테스트 페이지를 확인할 수 있으며, 자동적으로 해당 페이지가 로딩된다.

    이 방식을 통해 테스트 페이지를 로딩할 경우, 실시간으로 코드의 변화를 화면으로 확인할 수 있다.

     

    프로젝트 테스트 하기

    프로젝트를 테스트 하는것도 커맨드 라인 명령어로 가능하다.

    위의 명령어는 Jest를 실행시킨다. Jest는 매우 효율적은 테스트 관리 툴이고, .test.ts 혹은 .spec.ts 로 정의된 파일들에 대해서 모두 테스트를 진행한다. npm run start 와 동일하게 한번 실행하면 위의 파일들이 변경 될 때마다 새롭게 테스트 한다. npm run startnpm run test 를 동시에 실행시키면 코드의 변화를 한번에 살펴보면서 테스트 결과도 볼 수 있다.

     

    프로덕션 빌드하기

    npm run start 를 통해 실행할 경우, 완전한 최적화가 되지 않은 상태로 프로젝트를 실행하게 된다. 유저에게 직접 프로젝트를 서비스 할 때에는 가능한한 빠르고 가벼운 상태로 전달해야한다.

    프로덕트 빌드를 하고 싶을 때에는 아래의 명령어를 실행하면 된다.

    위의 명령어를 실행할 경우 최적화된 JS 와 CSS 파일을 만들어 내며 ./build/static/js./build/static/css 에 각각 저장된다.

    대부분 개발 시간동안 위의 명령어를 사용할 일은 없으나, 중간 중간 프로젝트의 최종 크기 등을 확인하고 싶을 때에 사용할 수 있다.

     

    컴포넌트 생성하기

    본 문서에서는 예제로 Hello 컴포넌트를 만들어보도록 하자. 해당 컴포넌트는 아무 사용자의 이름이나 받아 들이며 name 변수에 저장한다. 추가 옵션으로 느낌표를 표시하기 위해 enthusiasmLevel에 느낌표 갯수를 받아 들인다.

    <Hello name="Daniel" enthusiasmLevel={3} /> 과 같은 코드를 작성했다고 가정하자. 해당 코드는 <div> Hello Daniel!!! </div> 와 같은 형태의 HTML 코드로 렌더링이 되어야 한다. enthusiasmLevel 이 정의되지 않았다면, 느낌표를 기본적으로만 추가하고, enthusiasmLevel 이 0 이거나 음수일 경우 에러를 내도록 하자.

    Hello.tsx 의 코드는 다음과 같다

    Props 로 명명한 새로운 타입 선언을 확인하자. 해당 타입을 통해서 컴포넌트의 매개변수를 입력 받을 것이다. name 변수는 반드시 요구되는 string 이고, enthusiasmLevel 은 옵션으로 필요한 number 이다. (변수의 이름 뒤에 ? 를 붙임으로써 옵셔널 변수임을 명시할 수 있다.)

    위의 코드와 같이 Hello 를 스테이트 관리가 필요 없는 함수형 컴포넌트 (Stateless Function Component, SFC) 로 선언할 수 있지만, 리액트 컴포넌트로 선언할 수도 있다. 위의 Hello 함수는 Props 를 인자로 받아들이며, PropsenthusiasmLevel 변수가 선언되어 있지 않을 경우에는 1로 초기화한다.

    아래는 함수형이 아닌 클래스형으로 선언한 모습이다.

    커스텀 컴포넌트가 스테이트 관리를 필요로 할 때 클래스 형으로 선언하는 것이 유용하다. 위의 예제에서는 스테이트 관리가 필요없다. (단순히 object로 선언만 해두었다.) 그러므로 Hello 컴포넌트는 SFC 로 구현하는 것이 간결하다. 자세한 내용은 이후에 Redux와 함께 배우도록 한다.

    새로운 컴포넌트를 작성해 보았으므로 이제는 index.tsx 에 대해서 자세히 알아보고 <App /> 을 대신해서 <Hello .../> 를 렌더링 하도록 해보자.

    일단 index.tsx 파일의 최상단에 아래의 코드를 추가하여 Hello 컴포넌트를 임포트 한다.

    이후 render 함수의 인자를 변경한다.

    Type assertions

    본 섹션에서 마지막으로 짚고 넘어갈 부분은 document.getElementById('root') as HTMLElement 코드이다. 이러한 문법을 type assertion 이라고 부르며, 캐스팅이라고도 한다. 타입스크립트의 타입체커가 일을 하기 전에 미리 어떤 타입인지를 알고 있을 때, 타입스크립트를 통해 명시적으로 표현할 때 유용한 방식이다.

    getElementById 함수가 반환하는 값은 HTMLElement 혹은 null 의 두 종류이므로 위와 같은 문법을 사용해야 한다. id 에 해당하는 엘리먼트를 발견하지 못할 경우 null 을 반환한다고 가정하자. 우리가 사용하는 프로젝트 코드에서는 getElementById 가 무조건 성공할 것임을 알고 있으므로, 타입스크립트의 컴파일러에게 해당 사실을 알려주어야 한다. 이때 사용하는 것이 as 문법이다.

    타입스크립트는 제일 끝에 ! 를 붙이는 문법도 지원한다. 사용하려는 변수의 타입 판별에서 nullundefined 는 제외하라는 명령이다. 이러한 문법을 사용할 경우 위의 코드를 document.getElementById('root')! 로 변경할수도 있으나, 좀더 명시적으로 어떤 컴포넌트가 반환되는지 표기하기 위해 위와 같은 코드를 사용하였다.

     

    Adding style

    create-react-app 을 통해 세팅한 프로젝트에서 스타일을 추가하는 것은 매우 간단하다. 우리가 생성한 Hello 컴포넌트에 스타일을 추가하기 위해서, 간단한 CSS 파일을 생성하면 된다.

    아래의 코드를 ./src/components/Hello.css 를 만들어서 추가하도록 하자.

    Create-react-app 을 통해 만들어진 프로젝트에서 사용되는 툴들을 CSS 파일을 곧바로 임포트하여 사용할 수 있도록 한다. 빌드를 하는 시점에서 모든 .css 파일들이 아웃풋 파일들에 통합된다. 간단하게 ./src/components/Hello.tsx 에서 CSS 파일을 임포트 하면 된다.

     

    Writing tests with Jest/Enzyme

    Hello 컴포넌트를 만들때에 몇가지 가정이 있었다. 무엇이 있었는지 확인해보자.

    • <Hello name='Daniel" entusiasmLevel={3} /> 과 같은 코드를 작성할 때, 실제 컴포넌트에서는 <div>Hello Daniel!!!</div> 형태의 코드를 렌더링 한다.
    • enthusiasmLevel 이 정의되지 않은 상태인 경우, 하나의 느낌표만을 표시한다.
    • enthusiasmLevel 이 0이거나 음수인경우, 에러로 처리한다.

    이러한 가정들을 컴포넌트를 테스트 하는 테스트코드에 이용할 수 있다.

    테스트 코드를 작성하기 전에 일단 Enzyme을 설치하자. Enzyme 은 리액트 환경에서 컴포넌트들이 어떻게 동작하는 지 테스트를 쉽게 할 수 있도록 도와준다. 기본적으로 본 문서를 통해서 만들어진 프로젝트는 jsdom이라고 불리는 DOM을 시뮬레이션 해주는 라이브러리를 포함하고 있으며, 브라우저 없이 DOM의 동작 방식을 테스트 할 수 있도록 되어있다. Enzyme은 이와 유사하지만, jsdom 위에서 특정한 쿼리를 사용해 커스텀 컴포넌트를 테스트 할 수 있도록 도와준다.

    개발 중에서만 사용하는 도구이므로 개발 모드로 설치하도록 하자.

    enzyme 뿐만 아니라 @types/enzyme 도 설치한 것을 확인하자. enzyme 패키지는 실행시키기 위한 자바스크립트 코드가 포함된 패키지이고, @types/enzyme 은 이를 사용하기 위한 옵션들이 정의된 .d.ts 확장 파일들을 포함한 패키지이다. 이를 통해서 enzyme을 어떤 방식으로 사용하는지 타입스크립트 코드내에서 확인 할 수 있도록 한다. @types 패키지에 대한 자세한 내용은 홈페이지에서 확인하자.

    react-addons-test-utils 또한 필수적으로 설치해야 한다. 이 툴은 enzyme 에서 요구하는 툴 중 하나이다.

    Enzyme을 설치했다면 테스트 코드를 작성해보도록 하자. 일단 src/components/Hello.test.tsx 파일을 생성한 뒤 아래의 내용을 작성하자.

    위의 테스트 코드 심하다고 말할 수 있을 정도로 간단하고 기초적이다. 위의 코드에서 하고자 하는 일들은 직접 파악할 수 있어야 한다.

    개인적인 추가팁 (공식 문서에 빠진 부분)

    위의 코드를 실행하기 위해서는 추가적인 패키지를 설치해야 한다.

    위의 설치 명령어는 리액트 16버전 이후를 기준으로 설치하는 것이며, 자신의 리액트 버전에 맞는 패키지는 enzyme github 에서 확인하도록 하자.

    이후 아래의 코드를 src/components/Hello.test.tsx 의 상단에 추가하자.

    코드를 추가했다면 아래의 명령어를 통해 테스트 해볼 수 있다.

    src 는 타입스크립트 코드가 포함되어 있는 폴더를 지정하는 것이다. src 대신에 components 로 지정할 경우 components 가 포함된 폴더 혹은 파일들만 테스트 하고, Hello 만 입력할 경우 Hello 컴포넌트만 테스트 할 수 있다.

    문서의 초반부에서 설명 했듯이 한번 명령어를 실행 하면 계속해서 타입스크립트 코드의 변화를 확인하고, 변화가 생길때마다 자동으로 테스트 한뒤 테스트 결과를 알려준다.

     

    스테이트 관리 추가하기

    리액트를 사용하여 만든 프로젝트가 단순히 한번 화면을 구성하는 경우에만 데이터를 fetch하는 수준이라면, 여기서부터는 보지 않아도 된다. 하지만, 그보다 훨씬 복잡한 앱을 구성하는 경우에는 스테이트 (state) 관리가 반드시 필요하게 된다.

    일반적인 스테이트 관리

    리액트 자체적으로 여러가지 뷰들을 생성하고 통합하기 편한 라이브러리 이지만, 앱 내부에서 서로 다른 컴포지션간에 데이터의 동기화를 시켜주지는 못한다. 리액트의 데이터 흐름은 상위 컴포넌트에서 하위 컴포넌트로 props를 통해 아래로만 전달하는 방식이다.

    리액트 자체적으로는 상태 데이터 동기화를 제공하지 않기 때문에, 리액트 커뮤니티에서는 Redux나 MobX 같은 라이브러리를 사용한다.

    Redux 는 중앙화되고 변경 불가능한 형태로 데이터를 저장하고 동기화하며, 데이터를 업데이트 하면 애플리케이션의 뷰를 다시 렌더링 하도록 한다. 상태정보 (state) 는 불변한 상태를 유지하며, 업데이트를 원할 경우에는 명시적인 액션 메시지를 통해서 reducer라고 불리는 함수를 통해 변경하게 된다. 명시적으로 변경을 전달하기 때문에 실제 액션이 앱에 어떻게 동작하는지 알기 쉽다.

    Mobx는 상태정보가 props를 통해 전달되며 다른곳에서 관찰 가능한 형태로 관리하는 functional reactive pattern을 유지한다. 상태정보를 관리하고 싶은 컴포넌트 간의 동기화는 간단하게 observable을 표시하면 된다. 이미 타입스크립트로 구현이 되어 있어서, 곧바로 사용하기에 편리한 장점이 있다.

    둘 모두 장단점이 있다. 리덕스의 경우 훨씬 많은 사용자를 가지고 있기 때문에 다양한 사용법을 확인할 수 있으므로, 본 튜토리얼에서는 리덕스를 사용하도록 한다; 시간이 될 경우 둘 모두 직접 사용해 보기를 권장한다.

    이후에 나오는 내용들은 배우기 어려울 수 있으므로, 리덕스 문서를 통해서 직접 공부하기를 매우 권장한다.

    세팅

    애플리케이션이 동작하는 동안 내부 상태정보가 변화하지 않는 다면 리덕스를 도입할 필요가 없다. 상태변화를 요구하는 행동들에는 타이머나 버튼과 같은 요소들이 있을 수 있다.

    튜토리얼을 진행하기 위해서 Hello 모듈에 두 가지 버튼을 추가하여 enthusiasm level을 조절해보도록 하자.

    Redux 설치

    리덕스를 프로젝트에 추가하기 위해서 일단 reduxreact-redux 를 설치하고 타입도 함께 설치한다.

    리덕스는 자체적으로 타입 설정 파일인 .d.ts 파일을 설치하므로 굳이 @types/redux 를 설치할 필요 없다.

    앱의 상태 정보 정의하기

    첫 번째로 할 일은 리덕스를 이용해 저장할 상태정보의 형태를 정의하는 것이다. src/types/index.tsx 를 생성하고 우리가 사용할 상태정보를 정의하도록 하자.

    languageName 에는 현재 프로그램이 어떤 언어로 작성되었는가에 대한 정보를 저장할 것이고, enthusiasmLevel 은 앞서 사용된 용도대로 다양하게 변화하는 숫자가 될 것이다. Hello 컴포넌트에서 사용한 매개변수와 실제 우리가 정의한 상태정보의 형태가 다른 이유는 추후에 설명하도록 한다.

    액션 추가하기

    상태정보 변경을 위해 일단, 예제 앱에서 처리할 액션들을 src/constants/index.tsx 에 정의하도록 하자.

    const / type 패턴을 사용하여 문자열을 정의할경우 타입스크립트에서 쉽게 사용할 수 있으며, 향후에 다시 수정하기 용의하다.

    이제 실제로 액션을 수행할 함수들을 src/actions/index.tsx 에 작성하자

    위의 코드를 통해 증감의 액션을 표현할 타입들을 정의하였다. 두 타입을 한번에 표현하기 위한 EnthusiasmAction 타입도 정의하였다. 이후 실제로 액션 정보를 생성하는 두가지 함수를 작성하였다.

    일단 기본 코드를 작성하였으므로 redux-actions와 같은 라이브러리 들에 대해서 알아보는 것도 좋다.

    리듀서 (Reducer) 추가하기

    리듀서는 단순히 변화를 관장하는 함수이다. 앱에서 사용하는 상태정보의 복사본을 생성하고 정보를 변경한다. 리듀서는 완전한 pure function이다.

    리듀서는 src/reducers/index.tsx 에 작성하도록 한다. 증가함수는 enthusiasm level을 1씩 증가 시키고, 감소함수는 1씩 감소시키도록 한다. 이 때 1 미만으로 떨어지지 않도록 한다.

    코드에서 오브젝트 스프레딩 (...state) 기법을 사용한 것을 확인하자. 이를 통해서 기존 스테이트의 얕은 (shallow) 복사본을 생성하였으며, enthusiasmLevel 은 직접 설정하였다. enthusiasmLevel 을 복사본 뒤에 선언하여야 업데이트 할 수 있음을 명심하자.

    리듀서를 작성한 뒤에 리듀서에 대한 테스트를 해보고 싶을 수도 있다. 리듀서는 순수 함수이기 때문에 임의의 데이터를 전달해서 테스트 해볼 수 있다. Jest의 toEqual 메소드를 통해 테스트 하는 방법을 직접 찾아보도록 하자.

    컨테이너 만들기

    리덕스를 사용할때는 컴포넌트 뿐만 아니라 컨테이너도 생성한다. 컴포넌트는 데이터를 통해서 화면을 구성하는데 사용되고, 컨테이너는 컴포넌트를 포함한 여러가지 데이터와 상태정보를 관리하는데 사용된다. 이러한 컨셉에 대해서는 이 문서를 참조하도록 하자.

    일단 src/components/Hello.tsx 를 수정하여 상태정보를 관리하도록 하자. 두 가지의 콜백 속성인 onIncrementonDecrementProps 에 정의한다.

    새롭게 추가한 두가지의 콜백 속성을 새로 사용할 버튼에 할당하자.

    일반적으로 애플리케이션에 새로운 함수를 적용하기 이전에 onIncrementonDecrement 가 버튼 클릭에 대해서 제대로 호출 되는지에 대한 테스트를 작성해보는 것도 좋은 아이디어 있다.

    컴포넌트에 대한 새로운 정의를 완료하였으므로, 이를 총괄하기 위한 컨테이너를 작성해보자. 컨테이너는 src/containers/Hello.tsx 에 작성하도록 한다.

    여기서 중요한 두가지 사항은 원래 작성해둔 Hello 컴포넌트와 react-redux 패키지에서 가져온 connect 함수이다. connect 함수는 Hello 컴포넌트를 컨테이너로 변경하는데 사용된다. 컨테이너를 생성하는데는 아래의 두가지 함수가 사용된다.

    • mapStateToProps 는 현재 상태 정보 데이터를 컴포넌트에서 사용하는 형태로 변경한다.
    • mapDispatchToPropsdispatch 함수를 통해 액션을 생성하는 콜백 함수들을 생성한다.

    현재까지 Hello 컴포넌트는 두가지의 상태정보를 관리하도록 구현하였다. languageNameenthusiasmLevel 이다. 실제로 Hello 컴포넌트에서 사용하는 props는 nameenthusiasmLevel 이다. mapStateToProps 는 상태정보가 저장된 데이터로부터 필요한 데이터를 추출하여 조정한뒤 컴포넌트에서 사용할 props 형태로 변경해준다. 일단 코드를 확인해보도록 하자.

    mapStateToPropsHello 컴포넌트에서 필요한 4가지 props 들 중 2가지의 요소만 생성해내는 것을 확인 할 수 있다. onIncrementonDecrement 콜백에 대해서는 새롭게 생성해줄 필요가 있다. 디스패치를 담당하는 함수인 mapDispatchToProps 함수를 작성하여 Hello 컴포넌트에서 수행할 액션들을 생성하고 전달하도록 하자.

    이제 connect 를 통해서 mapStateToPropsmapDispatchToProps 를 사용하는 컨테이너를 생성하자.

    저장소 생성

    src/index.tsx 로 돌아가서, 앞서 만든 모듈들을 모두 추가 한뒤에 초기 상태 정보를 가진 저장소를 생성하자.

    여기서 store 가 중앙 저장소를 담당한다.

    이제 src/components/Hello 대신 src/containers/Hello 를 사용하여 react-redux의 Provider 를 통해 props 를 구성해보도록 하자.

    Hello 컴포넌트는 더이상 직접적인 props를 전달받을 필요가 없다. 위에서 작성한 코드에서 connect 를 통해 앱의 상태정보를 관리하도록 했기 때문이다.

     

    Ejecting

    create-react-app 을 통해 생성한 프로젝트에 적용하기 어려운 사용자 컴포넌트가 있다면, eject를 통해 빠져나올 수 있다.

    eject 이후에는 되돌리기가 불가능 하므로 그 전에, git 과 같은 버전 관리 시스템을 통해 미리 이전 상태를 저장하도록 하자.

     

    Next steps

    create-react-app 은 많은 장점을 가지고 있으며 README.md 에 여러가지 요소들이 정리가 되어있다.

    Redux에 대한 더 자세한 내용은 공식 홈페이지 를 통해서 공부하도록 하자. MobX도 마찬가지이다.

    eject를 수행한 뒤에 Webpack 에 대해서 더 자세히 알고 싶을 경우에는 React & Webpack walkthrough 를 확인하자.

    특정 상황에서 라우팅이 필요할 경우에는 react-router가 가장 유명한 Redux 용 라우터 이다. 보통 react-router-redux와 결합하여 사용된다.

     

    내가 다음 할일

    본 문서에서 다룬 예제 코드에 대한 상세 분석

Designed by Tistory.