여기서부터는 개발 단계에서 겪은 문제 상황과 해결 방법을 적어보겠다.
이미지를 넣는데, 반응형으로 창을 줄일 때 이미지가 비율이 지켜지지 않고 좌우로 구겨지기만 해서 못생긴 이미지가 되어 곤란했다.
그래서 검색한 결과, 'aspect-ratio'라는 속성이 있었다!
aspect-ratio란?
일정 비율을 유지하게 하는 속성
//ex)
import styled from 'styled-components'
const Image = style.img`
width: 50%;
aspect-ratio: 1/1;
위처럼 작성하면, 너비는 부모 요소의 50%가 되고, 높이는 그에 맞춰 1:1의 비율로 유지된다.
1/1말고도 4/3, 16/9 등 마음대로 비율을 설정할 수 있다.
이번 해커톤에서 이 속성을 애용하였다!
최종적으로는 우리 서비스에서 지도가 빠지게 되었지만, 중간까지만 해도 지도를 통한 위치 안내 서비스가 있어 페이지에 지도를 임베드해야했다.
두 가지의 선택지가 있었다.
네이버 & 카카오 맵 API 비교
(1) 네이버 지도
- 횟수 제한 유료
- 공식 문서가 복잡하고 가독성이 낮음
- 필요한 기능을 스스로 뽑아서 사용해야 함
- 국내에서 가장 많이 사용됨
(2) 카카오 지도
- 횟수 제한 유료
- 공식 문서가 잘 쓰여있음
- 공식 사이트에서 실제로 코드를 테스트 해볼 수 있음
- 필요한 여러가지 기능들이 잘 분류되어 있음
- 네이버 지도보다는 덜 사용됨
주변에서도 카카오 지도를 추천하기도 하고, 훨씬 안내가 친절하다는 평이 자자했지만 나는 평소에 네이버 지도에 더 익숙하기도 했고 왠지 어려운 것에 도전(?) 해보고 싶어서 처음에는 네이버 지도로 구현을 시도하였다.
페이지에 동적 지도를 띄우는 것까지는 성공했으나, 그 다음부터가 문제였다.
내가 구현해야 하는 것은
- 특정 위치에 핀 띄우기
- 핀을 누르면 관련 정보를 불러오기
- gps를 이용해 사용자의 위치를 기준으로 초기 지도 위치 설정하기
크게 이 정도였는데, 해당 기능들이 네이버 지도 공식 문서에 꽁꽁 숨겨져있어 찾기가 어려웠던 것이다!!
그래서 결국 반신반의하며 카카오 지도로 넘어가 문서를 읽어보았다. 그런데 이게 웬일, 너무나도 쉬운 것 아닌가!
역시 사람들이 추천하는 데는 이유가 있다...

https://apis.map.kakao.com/web/
이건 공식 문서에 더 친절하게 설명되어 있으니 생략하도록 하겠다. 정말 그대로 보고 따라하면 된다.
카카오 지도 API를 사용하려면 기본적으로 script 태그로 API를 불러와야 한다.
코드는 다음과 같다.
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=(발급받은 API 키)"></script>
나는 리액트를 사용해 개발 중이었기 때문에, index.html의 body 안에 해당 태그를 넣어주었다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>BaroNow</title>
<!-- <script type="text/javascript" src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpClientId=ofqftr9ir5"></script> -->
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=f9bc2694b79adc41830c94462233a59b"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
script 태그를 넣은 최종 index.html 파일이다.
카카오 script 태그 위에는 네이버 script 태그의 흔적이 있다...
다음은 지도를 초기화하는 initializeMap() 함수이다.
//지도를 초기화
const initializeMap = () => {
// 현 위치의 위도와 경도
let lat;
let lon;
// HTML5의 geolocation으로 사용할 수 있으면
if (navigator.geolocation) {
console.log("!!!geolocation을 사용할수 있어요!!!");
// GeoLocation로 현 위치 얻어오기!!
navigator.geolocation.getCurrentPosition(function(position) {
lat = position.coords.latitude; // 위도
lon = position.coords.longitude; // 경도
console.log("< lat: ", lat, "lon: >", lon);
const locPosition = new window.kakao.maps.LatLng(lat, lon), // 마커가 표시될 위치를 geolocation으로 얻어온 좌표로 생성
message = '<div style="padding:5px;">현 위치</div>'; // 인포윈도우에 표시될 내용
// 마커와 인포윈도우를 표시합니다
displayMarker(locPosition, message);
});
// geolocation을 사용할 수 없으면 -> 그냥 기본 위치만 보여주기
} else {
console.log("geolocation을 사용할수 없어요..");
const locPosition = new window.kakao.maps.LatLng(33.450701, 126.570667),
message = 'geolocation을 사용할수 없어요..'
displayMarker(locPosition, message);
}
const container = document.getElementById('map');
const options = {
//기본 위치로 현 위치를 지정
center: new window.kakao.maps.LatLng(33.450701, 126.570667),
level: 3,
};
map = new window.kakao.maps.Map(container, options); // map을 전역 변수에 할당
// 지정된 위치에 마커 추가
addMarkers();
};
이때 사용자의 현 위치를 가져오기 위해서 'geolocation API'를 사용한다!
API를 사용하기 위해서 API를 사용해야 한다니...
⭐️ geolocation API란?
HTML5부터 기본적으로 내장되어있는 기능으로, 사용자의 위치 정보를 불러와 사용할 수 있게 해주는 기능
geolocation 초기 설정
기본적으로 navigator.geolocation로 불러와 사용할 수 있다.
geolocation이 사용 불가할 시의 예외 처리를 위해 다음과 같은 코드 틀을 기본적으로 짜 주는 것이 좋다.
if (navigator.geolocation) {
/* 위치정보 사용 가능 */
} else {
/* 위치정보 사용 불가능 */
}
현 위치 불러오기
// GeoLocation로 현 위치 얻어오기 - 전체 코드
navigator.geolocation.getCurrentPosition(function(position) {
lat = position.coords.latitude; // 위도
lon = position.coords.longitude; // 경도
console.log("< lat: ", lat, "lon: >", lon);
const locPosition = new window.kakao.maps.LatLng(lat, lon); // 마커가 표시될 위치를 geolocation으로 얻어온 좌표로 생성
const message = '<div style="padding:5px;">현 위치</div>'; // 인포윈도우에 표시될 내용
// 마커와 인포윈도우를 표시합니다
displayMarker(locPosition, message);
});
getCurrentPosition()함수로 사용자의 현 위치를 불러올 수 있다.
그런데 해당 함수 안에 또 function(position)의 함수가 인자로 들어가 있는 것을 볼 수 있다. 정확한 원리를 몰라도 대충 코드를 보면 저 position으로 어떻게 잘 다루면 되겠구나~ 하는 걸 알 수 있겠지만, 이왕 하는 거 작동 원리를 알아보자.
먼저 이렇게 함수의 인자 안에 다른 함수를 넣는 것을 콜백 함수(Callback Function)이라고 한다.
⭐️ 콜백 함수란?
특정 작업이 완료된 후에 실행되도록 설계된 함수
getCurrentPosition()은 비동기적 함수로, 값을 완전히 받아올 때까지 기다리지 않기 때문에 콜백 함수를 통해 위치 정보를 완전히 불러오고 나서야 그 값을 쓰도록 하는 것이다.
getCurrentPosition()가 위치 정보를 가져오면, 다음 콜백 함수에서 그 값을 position 이라는 인자로 받아 함수 내에서 값을 사용한다.
그렇게 함수 내에서 위도와 경도를 설정하는 등의 작업을 할 수 있다.
const locPosition = new window.kakao.maps.LatLng(lat, lon); // 마커가 표시될 위치를 geolocation으로 얻어온 좌표로 생성
const message = '<div style="padding:5px;">현 위치</div>'; // 인포윈도우에 표시될 내용
이 부분은 불러온 현 위치를locPosition 변수에 담는 부분이다. 카카오 맵의 함수를 사용한다.
그리고 메세지를 설정한 후 displayMarker(locPosition, message);를 실행하면 최종적으로 지도에 사용자의 현 위치가 핀으로 뜨게 된다.
지정된 위치에 마커(핀) 표시하기
// 지정된 위치에 마커를 표시하는 함수 - 전체 코드
function addMarkers() {
const positions = [
{ title: '페어리하우스', latlng: new window.kakao.maps.LatLng(37.5052112, 126.9666879) }
];
const imageSrc = "https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/markerStar.png";
for (let i = 0; i < positions.length; i++) {
const imageSize = new window.kakao.maps.Size(24, 35);
const markerImage = new window.kakao.maps.MarkerImage(imageSrc, imageSize);
new window.kakao.maps.Marker({
map: map,
position: positions[i].latlng,
title: positions[i].title,
image: markerImage
});
}
}
positions 객체 배열에 title, 즉 장소명과 latlng, 즉 경도와 위도 정보를 카카오지도 내장 함수로 담아준다.
위 코드에는 한 장소만 했지만 여러 개도 가능하다.
imageSrc에 마커로 표시될 이미지를 넣어준다.
그리고 나서 반복문으로 positions의 길이만큼 반복하여 설정한 모든 위치가 지도에 표시된다. 사실상 이 반복문에서는 건드릴 게 딱히 없다.
원래는 더 많은 기능을 사용했어야 했는데, 중간에 지도 기능을 없애기로 결정되어 여기까지만 알아보았다.
난 무엇보다도 CSS가 가장 어렵다..
같은 디자인이라도 구현할 수 있는 방법이 너무나 많고, 그 중에서 가장 최적의 방법을 찾는 것은 참 어렵고 헷갈리기 때문이다.
그리고 어떤 디자인을 구현하고 싶어도 CSS의 속성을 다 알지 못하니 쉽게 할 수 있는 것을 어렵게 수동으로 노가다하며 하는 경우가 많은 것 같다. (항상 구글링을 해야하는 개발자의 숙명..)
그래서 이번에 CSS 작업을 하다가 알게된 flex 속성들을 정리해보려고 한다.
flex-wrap: 한 줄에 모든 아이템이 담기지 않을 때 아이템 줄바꿈을 어떻게 할지 결정하는 속성
- nowrap
- wrap
- wrap-reverse
운동용품 아이템 카드를 정렬할 때 자주 사용했던 속성이다.(창 크기를 줄임에 따라 아이템이 밑 줄로 wrap 되록)
justify-content: 아이템들이 메인축 방향으로 어떻게 정렬될지 결정하는 속성
- flex-start
- flex-end
- center
- space-between
- space-around
- space-evenly
이중에서도 flex-start와 space-between을 많이 사용했던 것 같다.


특히 space-between은 '왼쪽에 필드명, 오른쪽에 값' 이런 형태로 아이템 카드나 프로필 카드를 만들 때 자주 사용되었다.
API를 호출하고 응답을 받는 함수를 js 파일에 따로 만들어서 jsx 페이지 파일에서 불러와 사용하고자 하였다.(코드의 분리를 위해)
그래서 처음에는 이런 식으로 막무가내로 코드를 작성했었다.
///에러가 나는 코드입니다///
// product.js
import axios from 'axios';
export function getRentalHistory(){
const response = axios.get(`https://${SERVER_URL}/products/${productID}/rentalhistories/`);
return response;
}
// Mypage.jsx
import { getRentalHistory } from '../apis/product';
...
setRantalHistory(getRantalHistory());
...
당연히 잘 작동될리가 없었다...
일단 axios.get()는 비동기 함수로 호출하면 Promise를 반환하고, getRentalHistory() 함수를 사용하는 Mypage.jsx에서도 처리를 해주지 않았기 때문이다!!!
다음과 같이 코드를 고쳐줘야 한다.
// product.js
import axios from 'axios';
export const getRentalHistory = async () => {
try {
const response = await axios.get(`https://${SERVER_URL}/products/${productID}/rentalhistories/`);
return response.data; // 인기 상품 목록 반환
} catch (error) {
console.error('인기 상품 목록을 가져오는 중 오류 발생:', error); // 콘솔에 에러 출력
return []; // 에러 발생 시 빈 배열 반환
}
}
// Mypage.jsx
import { getRentalHistory } from '../apis/product';
...
getRentalHistory(productID).then((data) => {
setRentalData(data);
});
...
일단 getRentalHistory() 함수를 정의하는 부분에서, await 키워드로 비동기 처리를 해주어야 한다. axios.get 작업이 끝날 때까지 대기하게 하는 것이다.
응답을 불러오는 것을 완료한 후에야 return이 진행되어 옳은 응답 값을 넘길 수 있다.
그리고 함수를 실제로 사용하는 곳에서도 비동기 처리를 해주어야 한다. 막무가내로 return값을 사용하면 안 되고, .then을 통해 값을 받은 후에 setRentalData()가 이루어지도록 하였다.
배포 플랫폼으로는 Netlify를 사용했다.

배포할 브랜치를 선택하고 deploy를 하였는데, 모종의 이유로 오류가 나 배포가 완료되지 않았다.
로그를 살펴보니 'no-unused-vars' 관련 경고 때문인 것을 알 수 있었다.

(따로 캡쳐한 오류 창이 없어 인터넷에서 가져옴)
구글링을 해 보니, 에러가 아니라 경고도 하나라도 있으면 deploy가 되지 않는 것 같았다.
그래서 처음에는 '언젠가는 쓰겠지~'하고 아까워서 냅둔 unused vars들을 수동으로 하나하나 지우기 시작했다.
그러나 그 개수가 너무 많았다...(업보빔)
그래서 빠르게 경로를 틀어 '분명히 로컬에서는 잘 돌아가는데 배포에서도 경고와 상관없이 배포되게 하는 방법이 있을 거야!!'라는 희망을 갖고 또 검색을 시작하였다.
아니나 다를까 방법이 있었다!
바로 package.json에 빌드 옵션을 추가 하는 것이었다.
"build": "CI=false"
package.json의 scripts의 build에 "CI=false"를 추가해주면 된다.
그러면 감쪽같이 경고가 모두 무시되고 deploy가 성공된다!
다른 곳에서도 onClick() 함수를 많이 썼지만 한 가지 예시를 들자면, 물품 페이지에서 판매자의 상점 프로필을 누르면 해당 상점 페이지로 이동하게 해야 했다.
이를 위해 원래 작성한 코드는 이렇다.
// 오류가 나는 코드입니다!
<OwnerStore onClick={handleStoreClick(sellerID)}>
코드를 작성하고나서 와~ 너무 잘 썼다 천재 아냐? 하고 생각했지만
페이지를 새로고침하자마자... 상점 프로필을 클릭하지도 않았는데 자동으로 상점 페이지로 넘어가는 현상이 발생했다.
처음에는 이유를 영문을 알 수 없었다..
왜냐하면
<LoginBtn onClick={handleLogin}>
이전에 이미 이런 식으로 코드를 작성했을 때 문제없이 작동되었었기 때문이다.
그래서 또 폭풍 서치를 한 결과, 원인은 바로 위의 함수는 인자를 필요로 하고, 밑의 함수는 인자가 필요 없는 함수이기 때문이었다.
더 정확히 말하면, 두 번째에서는 함수를 '참조'할 수 있게만 알려준 것이라면, 첫 번째에서는 함수 이름 뒤에 괄호를 사용하여 실제로 '호출'이 되게한 것이기 때문이다.
그치만 첫 번째 함수는 상점의 ID를 무조건 인자로 전달해주어야 하기 때문에 괄호를 쓰지 않을 수 없다.
그러므로 '화살표 함수'를 적용하여 호출을 지연시켜야 한다!
이를 적용한 코드는 다음과 같다.
<OwnerStore onClick={() => handleStoreClick(sellerID)}>
그냥 함수 앞에 '() =>'를 추가해주기만 해면 된다.
간단하지만, '함수의 원리'가 깊게 들어있는 것 같아 인상깊었다!