[원티드 프리온보딩 프론트엔드 과정] 1차 과제, 환율 계산기

♦️ 1차 과제 : 환율 계산기

프로젝트 소개

currencylayer API를 활용하여 Select Box 전환기와 Tab Select 전환기, 두 종류의 환율 전환기를 각각 동작하도록 구현하는 것을 목표로 하는 프로젝트입니다.

과제 repo 링크

https://github.com/On-Basic/currency-converter

디렉토리 구조

├── node_modules
├── .github
├── public
│   └── index.html
├── src
│   ├── components
│   │   └── componentName
│   ├── costants
│   ├── hooks
│   ├── pages
│   ├── styles
│   └── utils
│
├── .gitignore
├── package-lock.json
├── package.json
└── README.md```

구현사항

Select Box 전환기

☑️ currencylayer API 데이터를 실시간으로 활용

☑️ select box에서 option 값 선택해 저장

☑️ 수취국가에 따라 하단 환율값 변동

☑️ 송금액 input창에 조건에 부합하지 않는 값 입력 시, 팝업창 출력

☑️ Submit을 누르면 수취금액이 KRW, JPY, PHP 중 하나로 계산, 결과값 출력

☑️ utils 활용해 환율과 수취금액 소숫점 2째자리까지, 3자리 이상 되면 콤마(,) 처리

☑️ 수취금액을 입력하지 않거나, 0보다 작은 금액이거나 10,000 USD보다 큰 금액, 혹은 바른 숫자가 아니라면 “송금액이 바르지 않습니다"라는 에러 메시지를 alert 창으로 띄우도록 처리

기능별 영상

  • 실시간으로 currencylayer API data를 받아온 뒤 select 된 option 값(나라)에 따른 환율값 실시간 변동 구현
  • utils 활용해 환율과 수취금액 소숫점 2째자리까지, 3자리 이상 되면 콤마(,) 처리
  • 조건식에 따른 input 값이 올바르지 않을 경우 “송금액이 바르지 않습니다"라는 에러 메시지를 alert 창으로 띄우도록 처리


[원티드 프리온보딩 코스] 첫번째 과제는 2인이 조를 이뤄 하나의 기능(환율계산기)을 만들어야 하는 과제였다. 각 조원들이 하나의 기능을 함께 구현해야했기 때문에, 조원들 간에 충분한 소통이 필요한 부분이었다.

새로 배웠거나 고민했던 지점

🔹 utils를 이용하는 방법

utils를 적극적으로 사용한 적이 없었기에 팀원들과 어떻게 공통된 기능을 찾아서 나누어야 할지 초반부터 조금은 막막했었다. 또한 기초세팅 부분에서 생각보다 많은 시간을 소모했기에 논의를 많이 하지 못한 상태에서 일단 각자 기능을 구현하다 비슷한 기능이 있다고 생각되면 그때 함께 고민해보기로 하였다.

먼저, 환율 계산기를 맡은 우리조는 해당 컴포넌트 안에서 2자리 소숫점과 수취금액을 1000 단위로 끊어주는 메소드와 정규식을 setTotal 값에 한번에 넣어주는 방식을 사용했다.


  setTotal(result.toFixed(2).replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ","));

두번째 환율 계산기를 구현하는 팀에서도 같은 조건(소숫점, 천단위 콤마)의 기능을 구현해야 했기에, 논의 끝에 utils에 addComma 라는 동일 기능의 함수를 사용하기로 결정했다. utils의 addComma는 이렇게 생겼고


export function addComma(num) {
  return num.toLocaleString(undefined, { maximumFractionDigits: 2 });
}

addComma 함수에 number type의 인자를 받아toLocaleString()이라는 내장함수로 처리해주는 식이었다. 팀원이 찾아낸 방법인데, 처음 보는 형태라 검색해서 찾아보니 정규표현식과 toFixed를 사용하지 않아도 옵션만 정해주면 아주 간단하게 형 변환을 해주고 동시에 콤마도 찍을 수 있었다.

toLocaleString은 type이 number인 값에서 toLocaleString 메소드를 인자 없이 호출하면 자동으로 String으로 형 변환된 뒤 콤마가 찍히도록 만들어준다.(*레퍼런스 참고) 기본적으로 소수점 3자리까지만 나타나기 때문에, 소수점 2자리 까지만 원하는 우리는 2번째 인자로 maximumFractionDigits 프로퍼티를 가진 객체에 2 라는 값을 주어 넘겨주면 되었다.

import { addComma } from "../utils/comma";

const handleToAmountChange = (e) => {
    e.preventDefault();
    const calculate = inputs * exchangeRate;

    if (inputs === "" || inputs < 0 || inputs > 10000 || inputs % 1 !== 0) {
      alert("송금액이 바르지 않습니다.");
    } else {
      const result = addComma(calculate);
      setTotal(result);
    }
  };

그리고 사용하고자 하는 컴포넌트에 import로 불러와 최종 값을 담아주는 setTotal에 addComma로 넘겨준 값을 넣어주면서 소숫점과 콤마를 처리해줄 수 있도록 했다.

🔹 공통 함수로 API 호출하기

API를 호출했을 때 기본 응답값의 형태는 다음과 같다.

{
    "success": true,
    "terms": "https://currencylayer.com/terms",
    "privacy": "https://currencylayer.com/privacy",
    "currencies": {
        "AED": "United Arab Emirates Dirham",
        "AFN": "Afghan Afghani",
        "ALL": "Albanian Lek",
        "AMD": "Armenian Dram",
        "ANG": "Netherlands Antillean Guilder",  
        [...] 
    }
} 

먼저 데이터를 받아올 수 있는지 확인하기 위해 사용하고자 하는 컴포넌트 내에서 useEffectaxios를 이용하여 API를 호출했다. 보안상의 이슈로 API key는 환경변수.env에서 설정하고 불러오는 방식을 택했다.

axios를 사용한 이유
팀원이 axios를 사용해보고 싶다는 의사를 비췄기 때문이다. 반드시 axios를 사용해야 하는 정당한 이유가 있었다기 보단 단지 학습 목적으로 사용하게 되었지만, 다음에는 의존하는 라이브러리에 대한 사용을 신중하게 고려하고 장단점을 따져본 뒤에 선택할 생각이다.


useEffect(() => {
    async function getData() {
      try {
        const response = await axios.get(
          `http://api.currencylayer.com/live?access_key=${process.env.REACT_APP_CURRENCYLAYER_API_KEY}`,
        );
        const { data } = response;
        setExchangeRateDict(data.quotes);
      } catch (error) {
        console.error(error);
      }
    }
    getData();
  }, []);

데이터를 제대로 받아오고 있는지 확인이 끝나고 기능을 모두 구현한 상태에서 해당 API 호출을 공통 함수로 사용하고 싶다는 다른 조원들의 요청을 받았다. 생각해보면 다른 조원의 프로젝트에서 사용할 API 데이터가 동일했기 때문에 따로따로 받아올 필요가 없었다. 그래서 일단 API를 공통으로 받아올 호출 함수가 필요했기에 먼저 data 폴더 내에서 API만 호출하는 getApi 호출 함수를 만들었다. response.data 안에 있는 quotes 의 data만 가져오고 싶었기 때문에 API가 호출되면 response.data 로 데이터를 전달할 수 있도록 했다.


import axios from "axios";

export const getApi = async () => {
  const response = await axios.get(
    `http://api.currencylayer.com/live?access_key=${process.env.REACT_APP_CURRENCYLAYER_API_KEY}`
  );

  return response.data;
};

그리고 data를 받아와서 useState에 담아주고 error 처리도 해주는 custom hooks를 만들었다. 언제든 재사용이 가능하도록 하기 위해서다.


import { useEffect, useState } from "react";
import { getApi } from "../utils/api";

export default function useData() {
  const [data, setData] = useState(null);

  useEffect(() => {
    async function getData() {
      try {
        const { quotes } = await getApi();
        setData(quotes);
      } catch (error) {
        console.error(error);
      }
    }
    getData();
  }, []);

  return { data };
}

사용하고자 하는 컴포넌트에서 getApi를 import 하고 useEffect를 사용하여 setExchangeRateDict에 data를 받아오도록 처리했다.


const { data } = useData();

useEffect(() => {
    setExchangeRateDict(data);
  }, [data]);

🔹 구조분해 할당을 통한 코드 경량화

구조분해 할당 : '분해(destructuring)'는 '파괴(destructive)'를 의미하지 않습니다. 구조 분해 할당이란 명칭은 어떤 것을 복사한 이후에 변수로 '분해(destructurize)'해준다는 의미 때문에 붙여졌습니다. 이 과정에서 분해 대상은 수정 또는 파괴되지 않습니다. 배열의 요소를 직접 변수에 할당하는 것보다 코드 양이 줄어든다는 점만 다릅니다. *레퍼런스

이전에는 일일이 setState를 생성하여 함수로 값을 담아주는 방식을 사용했었다. 그러다보니, 코드를 짜는 입장으로서도 직관적으로 로직을 파악하기에도 복잡했고, 코드가 길어진다는 단점이 있었다.


const countryToCurrencyDict = {
  한국: "KRW",
  일본: "JPY",
  필리핀: "PHP",
};

function FirstConverter() {
  const [countryOption, setCoutryOption] = useState("한국");
  const toCurrency = countryToCurrencyDict[countryOption]; // KRW
  const [exchangeRateDict, setExchangeRateDict] = useState(null);
  const exchangeRate = exchangeRateDict && exchangeRateDict[`USD${toCurrency}`]; // USDKRW

이런 경우에, 구조분해 할당을 사용하면 좋을 것이라 생각했고 확연히 코드의 양이 줄게 되었다. 함께 페어 코딩을 했던 조원에게도 로직을 설명하는데에도 조금 더 수월했다.

회고

몇 번 팀 프로젝트를 경험했던 터라, 이번에는 조금은 수월하지 않을까 예상 했었는데 그건 대단한 착각이었다는 걸 다시 한번 느끼게 해준 시간이었다. 총 4명의 팀으로 각각 2명씩 조를 나눠 구현 조건이 조금씩 다른 환율 계산기를 구현해야 했는데, 초기 세팅하는 과정이 원활하지 않았고 각자 구현해야 하는 기능에 급급해 초반에 세팅하면서 세팅한 eslint와 prettier 이슈로 모든 팀원들이 고생을 많이 했다. 돌아보니 프로젝트를 시작하기 전 팀원들 간에 충분한 협의가 필요했다고 느낀다.

  • 공통으로 사용할 수 있는 부분은 무엇인지
  • 어떻게 분리를 하여 사용할 것인지
  • 부여받은 과제 뿐만 아니라 다른 팀의 과제도 파악하는 시간이 필요

처음 받은 과제에 당황하여 내가 담당한 환율 계산기를 구현하느라 다른 조 팀원들과 커뮤니케이션이 조금 부족했던 것 같아 아쉬움도 남는다. 또한, 공통 함수를 조금 더 적극적으로 사용해볼 걸 하는 작은 아쉬움도 있다. (utils를 처음 사용해보았기에, 괜히 두려움이 앞서 구현 속도가 느려진 점도 있었다.) 하지만 이제 첫주차 첫번째 팀 과제이기에 첫술에 배부를 수는 없으리라 생각한다. 각자 피곤하고 벅찬 하루하루를 보내고 있지만 앞으로도 서로 배려하며 끝까지 완주하길 기대한다.

Reference


JavaScript 내장 메소드를 사용하여 숫자 천단위마다 콤마 찍기
ES6 문법 정리-(6) 구조 분해 할당(Destructuring Assignment)
모던 JavaScript 튜토리얼, 구조분해할당

좋은 웹페이지 즐겨찾기