Reac.js 프로젝트 JS to TypeScript 포팅

Arenacast·2022년 5월 4일
2

typescript

목록 보기
1/1
post-thumbnail

포팅의 목적과 Javascript의 문제점

  • 파라미터의 타입 체킹 없음
  • 근본없는 변수들의 남발
  • props를 코드만 보고 타입을 추론하기 번거로움
  • 자동 완성 불편함
  • enum 미지원
  • 코드의 오류를 실행해보기 전까지 알수 없음
  • 오류와 버그를 찾는데 오히려 더많은 시간을 소비 생산성 저하로 연결
  • 규모에 따라 유지보수 및 추가 업데이트가 점점 나락으로 갈수 있음
  • 이런 단점들을 극복 하기 위한 타입스크립트의 도입
  • 타입스크립트를 안써본 사람은 있어도 한번만 쓴사람은 없을것이다

@types package 설치

  • npm install -g typescript
  • @types/react
  • @types/react-dom
  • @types/react-redux
  • ts-loader
  • 기존 js package 중 @types용 설치가 필요한 경우

Webpack 설정

  • ts, tsx regex 추가
const tsxRegex = /\.(ts|tsx)$/;
  • resolve extensions 추가
extensions: ['*', '.ts', '.tsx', '.js', '.jsx']
  • module 추가
{
	test: tsxRegex,
    exclude: /node_modules/,
    include: path.resolve(__dirname, './src'),
    use: [
    	{
        	loader: "ts-loader",
        }
   	]
},
  • babel loader 사용하는 경우
    npm intall @babel/preset-typescript 추가 설치 필요
{
    test: tsxRegex,
    exclude: /node_modules/,
    include: path.resolve(__dirname, './src'),
    use: [
        {
            loader: 'babel-loader',
            options: {
                presets: ['@babel/env', '@babel/react', '@babel/typescript'],
                plugins: [
                    ["@babel/transform-runtime", {
                        regenerator: true
                    }], ...plugin
                ],
            },
        },
    ]
},

tsconfig.json

{
  "compilerOptions": {
    /* Language and Environment */
    "target": "es5",	
    "jsx": "react",                                
    
    /* Modules */
    "module": "commonjs",                                
    "rootDir": "./src",                                  
    "moduleResolution": "node",                       
    "baseUrl": "./src",                                  
    "paths": {
      "@common/*": [ "common/*" ],
      "@scss/*": [ "scss/*" ],
      "@store/*": [ "store/*" ],
      "@interfaces/*": [ "interfaces/*" ],
      "@network/*": [ "network/*" ],
      "@pages/*": [ "components/pages/*" ],
      "@hooks/*": [ "components/hooks/*" ],
      "@helper/*": [ "components/helper/*" ],
    },                                     
    
    /* JavaScript Support */
    "allowJs": true,                                  
    
    /* Emit */
    "sourceMap": true,                                
    "outDir": "./build",                                   
    
    /* Interop Constraints */
    "allowSyntheticDefaultImports": true,             
    "esModuleInterop": true,                             
    "forceConsistentCasingInFileNames": true,            

    /* Type Checking */
    "strict": true,                                      
    "noImplicitAny": true,                            
    "strictNullChecks": false,                         
  
    /* Completeness */
    "skipLibCheck": true,                                 
  },
  "exclude": ["node_modules"],
  "typeRoots": ["node_modules/@types"],
  "include": [
    "./src/**/*"
  ]
}

기존 javascript로 된 코드를 포팅할때 Type Checking 항목옵션에 따라서 까다로운 이슈가 많이 생길수 있습니다.
특히 strictNullChecks: true인 경우가 그런데 일단 기존 코드를 크게 건드리지 않는 선에서의 포팅이 목적이므로 해당 항목을 false로 설정 하기를 권장 합니다.

path alias 설정

컴포넌트나 모듈등을 import할때 path를 간략히 설정해서 간결하게 쓸수 있게 하는 alias설정이 ts에서는 기존 js용 설정과는 다르게 추가해 줄 것이 1가지가 있습니다.
js에서는 webpack에서만 추가해주면 되지만 ts에서는 tsconfig.json에도 설정 추가가 필요 합니다.

  • tsconfig.json 추가
    webpack alias구성과 동일한 항목들로 다음 포맷처럼 추가해 줍니다.
"baseUrl": "./src",                                  
"paths": {
  	"@common/*": [ "common/*" ],
  	"@scss/*": [ "scss/*" ],
  	"@store/*": [ "store/*" ],
    "@interfaces/*": [ "interfaces/*" ],
    "@network/*": [ "network/*" ],
    "@pages/*": [ "components/pages/*" ],
    "@hooks/*": [ "components/hooks/*" ],
    "@helper/*": [ "components/helper/*" ],
},

.tsx(ts)에서 기존 처럼 동일하게 alias 사용이 가능해 집니다.

import MetaDoors from "@pages/main/MetaDoors";

Type별 interface 적용하기

  • 일반적인 Basic type들은 point: number, typeValue: number | string 등과 같이 타입만 추가해주면 됩니다.

  • props

// props interface 선언
interface IProps {
  mobileFooter: boolean
}

usage:
const MetaDoors: React.FC<IProps> = ({mobileFooter}) => {
or
const MetaDoors: ({mobileFooter}: IProps) => {
or
const MetaDoors: ({props}: IProps) => {
  • useState, Function 타입의 props
interface IProps {
  settingMenu: number, 
  setSettingMenu: React.Dispatch<React.SetStateAction<number>>,
  detail: string,
  handleAction: Function ( or ()=>void )
}

useState타입은 any로 선언해도 되지만 타입을 명확히 해줄려면 위와같이 선언 해줍니다.

  • input event 타입의 사용
const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    const inputValue = e.target.value;
    console.log(inputValue); 
}
// e.target을 파라미터로 넘기는 경우
const handleInput = (target: HTMLInputElement) => {
    const isChecked = target.checked;
    console.log(isChecked); 
}
// textarea 는 HTMLTextAreaElement
  • Mouse onClick event의 처리
<span className="btn-del" onClick={(e)=>handleTagCancel(e)}></span>
// 사용 하는 element의 타입을 적용해서 사용
e: React.MouseEvent<HTMLSpanElement, MouseEvent>
  • useRef 타입의 사용
    TypeScript에서 useRef의 용도는 이론적으로 2가지 제네릭 타입으로 설명됩니다.
    로컬변수 용도의 경우는 MutableRefObject .current의 값이 변경 가능하고
    DOM을 컨트롤 하기위한 용도의 경우는 RefObject 타입으로 .current의 값이 변경 불가하다는 차이가 있습니다.
    DomElement 타입은 초기값을 null로 초기화 해줍니다.

usage:

// RefObject (dom element)
const searchInput: React.RefObject<HTMLInputElement> = useRef(null)
const searchInput = useRef<HTMLInputElement>(null)
const myToolRef: React.RefObject<HTMLDivElement> = useRef(null)
const audioRef: React.LegacyRef<HTMLAudioElement> = useRef(null)
  
// MutableRefObject
const isMounted: React.MutableRefObject<boolean> = useRef()
isMounted.current = true or false

TS에서 호환성 이슈가 생기는 JS 라이브러리의 대처

  • marquee tag는 ts에서는 지원하지 않는 tag라고 에러를 발생시킵니다.
    타입스크립트용 marquee를 찾아서 사용하면 되는데 다른 방법으로는 .jsx 컴포넌트를 추가해서 사용하면 간단히 해결 가능합니다.
// MarqueeJs.jsx
import React from 'react';
import * as Util from "@common/UtilTs";


const Marquee = ({upperLimits,hideHeader}) => {

  return (
    <marquee className={hideHeader ? "top-rolling rolling-hidden" : "top-rolling"}>
      {upperLimits.length > 0 && upperLimits.map((val,idx) =>{
        return (
          <span key={idx}>{`${val.userInfo.name} ${val.nftSerialNo} ${val?.title}`}
            <em className="up">{Util.addComma(val.price)} $</em>
          </span>  
        )
      })}
    </marquee>
  )
}

export default Marquee;

기존 레거시 프로젝트는 빠르게 포팅하는게 목적이므로 상황에 따라서 이와같이 호환성 이슈를 비켜갈수도 있다는 용도로 봐주시면 되겠습니다.

Redux 사용시 createStore deprecated의 해결 방안

createStore가 deprecated 되었고 configureStore를 사용하라는 툴팁이 뜹니다.

  • @reduxjs/toolkit 패키지를 추가 하고 store 로드 코드를 다음과 같이 수정합니다.
//import { createStore } from 'redux';
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './store/modules';

//const store = createStore(rootReducer);// deprecated
const store = configureStore({reducer: rootReducer});

ps. 추가되는 내용이 있을때마다 업데이트 예정입니다.
from Jerry

profile
arenacast team blog

0개의 댓글