본문 바로가기
웹 프로그래밍/👨‍👨‍👧‍👧소셜 가계부

[React/Node.js/Express/MongoDB] 소셜 가계부 프로젝트 구현 일지: JavaScript → TypeScript 마이그레이션 3(가계부 페이지, Button 컴포넌트: onClick 함수 타입 지정, 자잘한 오류들 해결)

by 청량리 물냉면 2024. 4. 4.
반응형

이전 마이그레이션 관련 포스팅

이전에 이어서 이번에는 Transactions 페이지와 관련한 컴포넌트 마이그레이션을 진행해 볼 예정이다.

 

 

Transactions 페이지

파일 형식을 jsx에서 tsx로 바꾼 뒤에 에러가 뜨는 코드를 확인하고 수정해 주었다.

// Transactions.tsx 

import React, { useEffect, useState } from "react";
...
import { UserInfo } from "../interfaces/UserData";
import { StoreData } from "../interfaces/StoreData";
import { TransactionData } from "../interfaces/TransactionData";

export default function Transactions() {
  const transactionList: TransactionData[] = useSelector((state: StoreData) => state.transactions.transactions);
  const userInfo: UserInfo = useSelector((state: StoreData) => state.user.userInfo);
  ...
  const [data, setData] = useState<TransactionData[]>([]);

  ...

  return (
    <Section>
      <div className="container">
        <Header
          text={dateText}
          leftChild={<Button text="◀" onClick={decreaseMonth} color="grey" />}
          rightChild={<Button text="▶" onClick={increaseMonth} color="grey" />}
        />
        ...
    </Section>
  );
}

이전 마이그레이션 파트에서 자주 등장하던 `userInfo` 이외에,

리덕스 저장소에서 가계부 리스트를 가져와 저장하는 `transactionList`라는 변수가 새로 생겼다.

가계부 리스트 타입을 정의하기 위해 새로운 인터페이스를 생성했다.

 

// TransactionData.ts

export interface TransactionList {
  transactions: TransactionData[];
}

export interface TransactionData {
  uid: string;
  transaction_type: boolean;
  date: number;
  category: string;
  title: string;
  amount: number;
  memo: string;
}

어제 배웠던 대로 배열 내부에 객체가 들어가는 데이터 타입을 지정하기 위해 `TransactionData[]`를 사용했다.

`TransactionData` 내부에는 가계부에 사용되는 데이터의 모든 속성과 타입이 들어가 있다.

가계부 코드 짠 지 워낙 오래돼서 속성명이나 데이터 타입을 뭘로 정했는지 생각이 안 났다; 몽고DB에 데이터 저장할 때 작성해 둔 스키마를 참고해 타입 지정을 해주었다.

 


 

이제 `Transactions` 내부에서 해결할 수 있는 에러는 모두 해결되었다.

대신 `Transactions` 컴포넌트에서 불러와 사용하고 있는 공용 컴포넌트 `Button`에서 에러가 발생하고 있어서, 오류 수정을 위해 `Button.jsx` 파일로 이동해 보겠다.

Button 컴포넌트

// Button.jsx

import React from 'react';
import { MouseEventHandler } from "react";
import styled from "styled-components";

export default function Button({ text, type, onClick, color }) {
  return (
    <CustomButton type={type} onClick={onClick} color={color}>
      {text}
    </CustomButton>
  );
}

const CustomButton = styled.button`
  ...
`;

 

 

컴포넌트에 있어도 되고 없어도 되는 props 가 있다면 ? 문자를 사용

// Transactions.tsx
  ...
          leftChild={<Button text="◀" onClick={decreaseMonth} color="grey" />}
          rightChild={<Button text="▶" onClick={increaseMonth} color="grey" />}
  ...

 

`Transactions` 컴포넌트에서는 `Button` 컴포넌트에 text, onClick, color 총 3개의 props를 전달하고 있다.

그러나 `Button` 컴포넌트에서는 3개의 props에 type까지 총 4개의 props를 받는다. 

자바스크립트로 코드를 작성할 때는 필요 없는 props는 굳이 전달하지 않아도 되었는데, 타입스크립트에서는 정의된 그대로 props를 모두 전달해 주어야 하고 이 때문에 에러가 발생하고 있었다.

 

😲 해결

전에 TS 강의에서 타입스크립트의 선택적 프로퍼티에 대해 배운 적이 있다.

변수를 선택적 프로퍼티로 설정하기 위해서는 단순히 변수 뒤에 ? 기호를 붙여주기만 하면 된다.

이러한 선택적 프로퍼티 기능을 컴포넌트에서 props를 받을 때도 동일하게 사용할 수 없나 하고 찾아봤다.

import React from 'react';

type GreetingsProps = {
  name: string;
  mark: string;
  optional?: string;
};

function Greetings({ name, mark, optional }: GreetingsProps) {
  return (
    <div>
      Hello, {name} {mark}
      {optional && <p>{optional}</p>}
    </div>
  );
}

위 예시처럼 컴포넌트에서 props를 받을 때도 optional 기호를 사용할 수 있다고 한다.

(출처: https://react.vlpt.us/using-typescript/02-ts-react-basic.html)

위 코드대로 진행하기 위해 `Button.jsx`를 `Button.tsx`로 바꾸고 프롭스를 optional하게 지정해 주었다.

다른 속성은 쉽게 타입 지정을 했는데, onClick 함수는 어떻게 초기값 지정을 해야 하는지 헷갈렸다.

 

 

props로 전달하는 onClick 함수의 타입 지정하기

서치 해 보니 리액트 마우스 이벤트 핸들러 타입은 다음과 같이 저장되어 있다고 한다.

type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>>;

interface Props {
  onClick: React.MouseEventHandler<HTMLButtonElement>;
}

따라서 해당 `MouseEventHandler<HTMLButtonElement>` 코드를 그대로 타입으로 지정해 주었다.

(참고: https://velog.io/@eenaree/props-onClick-type)

type ButtonProps = {
  text: string;
  type?: string;
  onClick: MouseEventHandler<HTMLButtonElement>;
  color?: string;
};

export default function Button({ text, type, onClick, color }: ButtonProps) {
  return (
    <CustomButton type={type} onClick={onClick} color={color}>
      {text}
    </CustomButton>
  );
}

위와 같이 타입을 optional하게 지정해 주니 에러가 사라졌다!

 

 

🚫 에러: styled-components Could not find declaration file

jsx를 tsx로 바꾸면서부터 `styled-components Could not find declaration file`라는 에러가 뜨고, styled-component를 정상적으로 불러오지 못하는 에러가 발생했다. 

이는 TypeScript가 styled-components와 연관된 모듈에 대한 선언 파일을 가져오지 못했을 때 발생하는 문제였다.

에러를 해결하기 위해 간단하게 @type이 추가된 styled-components 모듈을 재설치해주었다.

npm i --save-dev @types/styled-components

 

😲 TypeScript를 사용하는 프로젝트에 모듈을 설치할 때 @type을 붙이는 이유

👉 @type 없이 설치할 경우 모듈 내부 코드에 대한 type이 정의되어 있지 않아 TypeScript 파일에서 type추론이 불가능하기 때문

(출처: https://garniel23.tistory.com/entry/%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0-styled-components-Could-not-find-declaration-file)

 

이렇게 styled-components를 불러오지 못하는 문제는 해결 완료 하였다.

 

 

🚫 에러: 'React' refers to a UMD global, but the current file is a module

이건 또 뭔지...?

`'React' refers to a UMD global, but the current file is a module` 에러 발생.

해결 방법은 간단했다. `import React from 'react'` 코드를 추가하면 된다.

(참고: https://student513.tistory.com/82)

이렇게 하니 에러는 사라졌다. 이제 남은 건 옆의 type오류...

 

 

🚫 에러:  이 호출과 일치하는 오버로드가 없습니다. 2의 오버로드 1, '(프롭스: {type?: "button" | submit" | "reset" | undefined; on Click?: MouseEventHandler <HTMLButtonElement> | undefined; color?: string | undefined; ... 271 more...; onTransitionEndCapture?: TransitionEventHandler <...> | undefined; } & {...; } & {...} & {...}; }): ReactElement <...>에서 다음 오류가 발생했습니다.

`<Button>` 컴포넌트 호출 시 인자로 전달된 값들이 해당 컴포넌트의 기대 props 값과 다르기 때문에 발생하는 오류였다. 

해당 오류를 해결하기 위해서는 `<Button>` 컴포넌트를 호출할 때 필요한 props를 정확히 전달해 주어야 한다.

오류 메시지를 잘 확인해 누락된 프로퍼티를 확인하고, 필요한 경우 optional로 설정해 주는 식으로 타입 오류를 해결했다.

이렇게 완성된 최종 `<Button>` 컴포넌트는 아래와 같다.

// Button.tsx

import React from "react";
import { MouseEventHandler } from "react";
import styled from "styled-components";

type ButtonProps = {
  text?: string;
  type?: "button" | "submit" | "reset";
  onClick: MouseEventHandler<HTMLButtonElement>;
  color?: string;
};

export default function Button({ text, type, onClick, color }: ButtonProps) {
  return (
    <CustomButton type={type} onClick={onClick} color={color}>
      {text}
    </CustomButton>
  );
}

const CustomButton = styled.button`
  ...
`;

이제 모든 코드가 에러 없이 잘 동작한다.

반응형