http://react18-week1.s3-website.ap-northeast-2.amazonaws.com
├─ README.md
├─ package.json
├─ public
│ └─ index.html
└─ src
├─ pages
│ └─ mission1
│ └─ mission2
│ └─ Home.jsx
├─ routes
├─ utils
├─ constants
└─ api
└─ App.js
└─ index.js
이번 환율계산기는 vsc에서 live share기능을 사용해 두 명이서 함께 코딩을 진행했다.
아예 페어프로그래밍으로 진행한 건 처음이었는데 걱정했던 것과 다르게 팀원분과 잘 해낼 수 있었고 좋은 경험이었다.
이번 과제는 조금씩 다른 환율 계산기 2개를 2명이서 한팀이 되어 각각 하나씩 맡았다.
한 레퍼지토리 안에서 각각의 계산기를 2명이 함께 작업하다보니, 자연히 중요해진 것은 공통으로 사용할 변수와 함수였다.
나름대로 어떤 기능의 util함수를 만들어 함께 사용할지를 정했는데, 의외로 함께 사용하기에 어려웠던 점이 있었다.
export const convertPrice = (stringPrice) => {
const decimal = Number(stringPrice).toFixed(2).replace(/(^\d*[.]\d{3}$) | ([^0-9.]) |(^\d*[.]{2})/, '');
const formatted = decimal.replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ',');
return formatted;
};
각자 기능을 구현한 뒤 util함수를 점검하기 위해 모였을 때, 계산기 2를 작업하는 팀이 사용한 함수 중 convertPrice와 비슷한 것을 찾을 수 있었다.
export const removeCommaAmount = (strAmount) => {
const stringAmount = strAmount.replace(/,/g, '');
const numberAmount = Number(stringAmount);
return { stringAmount, numberAmount };
};
convertPrice는 소숫점과 ,를 추가하는 반면
removeCommaAmount는 ,를 삭제하는 기능이지만,
removeCommaAmount의 리턴값에 추가로직을 더해 convertPrice와 비슷하게 사용하는 코드가 있어 공통으로 합칠 수 있을 것 같았다. 예를 들면,
const { stringAmount, numberAmount } = removeCommaAmount(value);
if (stringAmount.length > 17) return;
if (!checkRegax(stringAmount)) return;
const commaAmount = numberAmount.toLocaleString();
if (commaAmount === '0') {
onChangeAmount('');
} else {
onChangeAmount(commaAmount);
}
input값을 받아 removeCommaAmount로 콤마를 제거 한 뒤, 조건에 부합한다면 다시 콤마를 찍어 commaAmount라는 값으로 사용하는 것을 볼 수 있었다.
convertPrice에 정규표현식을 더해 가정 먼저 콤마가 있다면 삭제하는 로직을 추가한다면 가능했다.
다만, 어디서 어떻게 removeCommaAmount를 다른 방식으로 사용하는지를 일일히 찾기는 어려웠는데, 주어진 시간 내에서 작성한 코드의 구조를 최대한 바꾸지 않고 convertPrice와 removeCommaAmount함수를 합쳐 공통으로 사용하려면 .. 마치 자판기처럼 convertPrice에서 구조분해할당으로 필요한 리턴값을 가져다 사용할 수 있도록 해야하지 않나, 하는 생각이었다.
예를들면, 아래와 같이 필요한 형태를 convertPrice에서 모두 만들어 받아올 수 있도록 하는 것이다.
export const convertPrice = (stringPrice) => {
const decimal = Number(stringPrice).toFixed(2).replace(/(^\d*[.]\d{3}$) | ([^0-9.]) |(^\d*[.]{2})/, ''); //소숫점 둘째자리까지의 문자열 값
const formatted = decimal.replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ',');//소숫점 둘째 자리와 천단위 콤마를 포함한 문자열 값
const stringAmount = strAmount.replace(/,/g, '');//콤마를 없앤 문자열 값
const numberAmount = Number(stringAmount);//소숫점 없는 숫자값
const numberAmountComma = numberAmount.toLocaleString();//소숫점 없이 천단위 콤마를 포함한 문자열값
return { decimal, formatted, stringAmount, numberAmount, numberAmountComma };
};
그리고 위의 코드를 이를 사용해 변경하면 다음과 같을 것이다.
const { value } = e.target;
const { stringAmount, numberAmount, numberAmountComma } = removeCommaAmount(value);
if (stringAmount.length > 17) return;
if (!checkRegax(stringAmount)) return;
if (numberAmountComma === '0') {
onChangeAmount('');
} else {
onChangeAmount(numberAmountComma);
}
toLocaleString()메서드도 고려했으나, 정규표현식을 사용한 이유는 다음과 같다.
3,000원과 같이 정수로 계산된 수취금액이 딱 떨어질 때에도 소숫점 둘째자리까지 표시해주어야 한다. 그런데 아래와 같이 두 가지를 한꺼번에 하기 어려운 부분이 있었다.
function convertPrice2(stringPrice) {
const stringAmount = stringPrice.toFixed(2);
const result = Number(stringAmount).toLocaleString();//3,000
return result;
}
function convertPrice2(stringPrice) {
const stringAmount = stringPrice.toFixed(2);
const result = stringAmount.toLocaleString();//3000.00
return result;
}
정규표현식을 사용하면 인풋값으로 받은 string 타입의 값을 처리하기도 편리했고, 위와 같은 어려움을 해결할 수 있었다.
계산기 2를 작업한 팀은 인풋값을 string으로 받아야 했다. 숫자를 입력하면 입력창에서 자동으로 ,가 천단위로 찍혀 표시되어야 했던 게 주 이유였다. 그리고 공통함수 사용과 예외처리를 고려했을 때 계산기 1을 작업할 때도 인풋값을 string으로 받는 것이 좋겠다고 생각했다.
https://currencylayer.com/의 API를 사용했다.
사용할 엔드포인트는 다음과 같았다.
Example API Request:
Run API Requesthttps://api.currencylayer.com/live
? access_key = YOUR_ACCESS_KEY
& currencies = AUD,EUR,GBP,PLN
따라서 공통으로 사용할 API 호출 함수를 만들었는데, 이 때 필요한 국가 문자열을 배열형태로 전달받아 join으로 묶어 파라미터를 완성해 호출하도록 했다.
const { default: axios } = require('axios');
const getExchangeRate = (countries) => {
const params = countries.length ? `¤cies=${countries.join()}` : '';
return axios.get(`http://api.currencylayer.com/live?access_key=${process.env.REACT_APP_API_KEY}${params}`);
};
export default getExchangeRate;
어떤 부분가지 상수화를 하고, 공통함수로 묶을 것인지에 대한 고민을 하게 되었다.
가독성
이 좋을 것이라고 생각했다.코드 수정이나 유지 보수에 있어 오타 등으로 혼란이 생길 우려를 방지
하기 위함일 것이다.//utils
export const convertExchangeRate = (params) => {
const { sendingRate, receivingRate } = params;
return receivingRate / sendingRate;
};
export const getRateKey = (selectedCountry) => `USD${selectedCountry}`;
//constants
export const USD = 'USD';
export const CAD = 'CAD';
export const KRW = 'KRW';
export const HKD = 'HKD';
export const JPY = 'JPY';
export const CNY = 'CNY';
export const PHP = 'PHP';
export const countryList1 = [JPY, PHP, KRW];
export const countryList2 = [USD, CAD, KRW, HKD, JPY, CNY];
export const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dev'];
<label htmlFor="reception" className={styles.item}>
수취국가:
<select name="reception" id="reception" onChange={selectCountry}>
<option value="KRW">한국(KRW)</option>
<option value="JPY">일본(JPY)</option>
<option value="PHP">필리핀(PHP)</option>
</select>
</label>
//contants
export const countryList1 = [JPY, PHP, KRW];
// Mission1.jsx
useEffect(() => {
api(countryList1).then((res) => setExchangeRates(res.data.quotes));
}, []);
{
"success": true,
"terms": "https://currencylayer.com/terms",
"privacy": "https://currencylayer.com/privacy",
"timestamp": 1432400348,
"source": "USD",
"quotes": {
"USDAUD": 1.278342,
"USDEUR": 1.278342,
"USDGBP": 0.908019,
"USDPLN": 3.731504
}
}
과제에서는 송금국가는 USD 고정이었기 때문에 USD 뒤에 붙는 국가만 지정해 밸류를 가져오면 되었다.
이 부분을 getRateKey라는 utils함수로 만들어주었다.
//utils
export const getRateKey = (selectedCountry) => `USD${selectedCountry}`;
여기서 고민이 되었다.
한 프로젝트에서 동일한 API엔드포인트를 사용한다면, 아래와 같이 사용하는게 응답값의 형태를 아는 상황에서 직관적일 수 있을 것 같다.
//Mission1.jsx
const handleRemit = (e) => {
e.preventDefault();
const val = inputRef.current.value;
validateRemit(val);
const rate = exchangeRates[`USD${selectedCountry}`];//
const result = convertPrice(String(rate * val));
setConvertedPrice(result);
};
반대로 프로젝트의 규모가 커지고 API의 엔드포인트 혹은 파라미터를 다양하게 조합해 사용해야 한다면 getRateKey와 그 외 다른 함수들을 만들어 사용하고 하나의 파일에서 API호출 방식을 관리하는 것이 더 효율적일 수 있을 것 같다.
아래 선택한 국가의 환율을 표시하는 영역에서는 getRateKey를 사용해주었다.
//Mission1.jsx
<p className={styles.item}>
환율 :
{`${convertPrice(String(exchangeRates[getRateKey(selectedCountry)]))} ${selectedCountry}`}
/USD
</p>
이번 과제는 기능 구현 자체보다는 어떻게 공통된 기능을 묶어 함수와 변수로 관리할지가 핵심이었던 것 같다.
하루 안에 완성해야하는 첫 과제였던만큼 팀원들과 최선을 다했지만, 돌아보고 나니 utils함수에 대해 더 세부적으로 미리 이야기를 나누고 일찍 검토해보면 더 좋았을 것 같다는 생각이 들었다.
와 같은 부분을 다음에는 더 세부적으로 최대한 생각하고, 얘기를 나누어보고 싶다.
첫 프로젝트 시작 당일 코로나 확진으로 다소 정신없는 하루였고 회의중에 전화를 받거나 시설로 이동하는 일들 때문에 미팅과 프로젝트 진행 중간에 흐름을 끊게 되는 일들이 있었어서 함께하는 팀원분에게 미안했다. 다들 배려해주셔서 감사했고, 컨디션을 회복하고 있으니 다시 팀원분들과 열정적으로 프로젝트에 참여해야겠다.