2. CRA(JS) -> CRA(TS) 변환 실습

최정우·2022년 4월 29일
0
post-custom-banner

1. 프로젝트 환경 세팅

  1. CRA

    • JS : npx create-react-app test
    • TS: npx create-react-app testts —template typescript

    [프로젝트 구조]

    [package.json] - 빨간색 줄들이 TS 프로젝트에 추가되어 있음.

    [tsconfig.json]

    {
      "compilerOptions": {
        "target": "es5",
        "lib": [
          "dom",
          "dom.iterable",
          "esnext"
        ],
        "allowJs": true,
        "skipLibCheck": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "noFallthroughCasesInSwitch": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "noEmit": true,
        "jsx": "react-jsx"
      },
      "include": [
        "src"
      ]
    }

  1. Storybook 추가
  • JS : npx -p @storybook/cli sb init
  • TS : npx -p @storybook/cli sb init --type react_scripts

[프로젝트 구조]


[스토리북 실제 화면] 왼쪽 : JS, 오른쪽 : TS


2.JS프로젝트 변환

  1. 타입스크립트 추가 → package.json dependencies 추가
# package.json
npm install --save typescript @types/node @types/react @types/react-dom @types/jest

# or

yarn add typescript @types/node @types/react @types/react-dom @types/jest
  1. tsconfig.json 추가
//tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "src/",
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react",   //
    "esModuleInterop": true
  },
  "include": [
    "src"
  ],
  "exclude": [
    "**/*.test.ts",
    "**/*.test.tsx"
  ]
}

관련 에러 해결

  • [디버깅] Cannot use JSX unless the '--jsx' flag is provided.ts(17004) 해결****
    참고 링크 : https://steadily-worked.tistory.com/632
    해결 법 : tsconfig.json 의 “jsx” : “react-jsx” 를 “react” 로 변경
  • (React) Argument type 'HTMLElement or null' not assignable to parameter type 'Element or DocumentFragment' 에러
    해결법 1 : tsconfig.json 의 “strict” : “true” 를 “false” 로 변경 (스트릭트 모드 사용 X 방법)
    해결법 2 : index.tsx 에서
    const root = ReactDOM.createRoot(document.getElementById('root')) 를
    const root = ReactDOM.createRoot(
    document.getElementById('root') as HTMLElement
    );
    로 타입을 지정
  1. 스토리북 컴포넌트 jsx → tsx 변환 (스토리북 빌드시 제공된 예시 Button 가지고 비교)

    1) Button.jsx 의 확장자를 tsx로 바꾸고, propTypes로 정의된 Prop를 Interface로 타입을 지정해준다.

// JSX 방식
import PropTypes from 'prop-types';

Button.propTypes = {
  primary: PropTypes.bool,
  backgroundColor: PropTypes.string,
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  label: PropTypes.string.isRequired,
  onClick: PropTypes.func,
};
// TSX 방식
interface ButtonProps {
  primary?: boolean;
  backgroundColor?: string;
  size?: 'small' | 'medium' | 'large';
  label: string;
  onClick?: () => void;
}
  1. jsx에서 any타입에 지정해두지 않은 속성을 받았다면, prop 설정에서 props를 옵셔널(?)를 붙여주도록 하자.( 위 타입스크립트 프롭스의 primary, size, onClick)

  2. jsx의 defaultProps 값은 실제 컴포넌트의 프롭스에서 값을 지정해준다

// JSX
Button.defaultProps = {
  backgroundColor: null,
  primary: false,
  size: 'medium',
  label: '',
  onClick: undefined,
};

export const Button = (
{ primary, 
backgroundColor, 
size, 
label, 
...props }) => {
  const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
  return (
    <button
      type="button"
      className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
      style={backgroundColor && { backgroundColor }}
      {...props}
    >
      {label}
    </button>
  );
};
// TSX
export const Button = ({
  primary = false,
  size = 'medium',
  backgroundColor,
  label,
  ...props
}: ButtonProps) => {
  const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
  return (
    <button
      type="button"
      className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
      style={{ backgroundColor }}
      {...props}
    >
      {label}
    </button>
  );
};
profile
누구나 할 수 있지만 아무나 못하는 일을 하자
post-custom-banner

0개의 댓글