2025년 2월 21일에 작성됨
이번에는 프론트엔드 연동에 대해 포스팅하려고 한다. 사실 프론트엔드 연동에 대해 깊이 생각해본 적이 없었다. 왜냐하면 지금까지 프로젝트를 많이 하지는 않았지만 프로젝트들을 진행하면서 단순히 JavaScript, CSS, HTML 이 세 가지만 주로 다루면서 깊게 생각하지 않고 써왔기 때문이다.
하지만 이번 프로젝트는 내가 직접 배우면서 제대로 시작해보고 싶었기에 다양한 프레임워크? 기술?들을 사용해보고 싶었다. 그래서 생각한 것이 React였다.
결과적으로 현재 프로젝트는 React를 사용하여 개발하고 있다. React에 대한 지식이 많이 부족하긴 하지만 직접 문제점들을 겪어나가면서 배워보는 것도 나쁘지 않겠다 싶어서 무작정 만들어보고 있다.
리액트(React)
그렇다면 React란 무엇일까? 에 대해 포스팅하려고 했지만 많은 블로그들을 찾아본 결과, React에 대한 설명들이 충분히 많이 기술되어있어서 이번에는 나의 생각을 조금 더 표현하고자 한다.
우선 React는 Facebook(현재 Meta)에서 만든 프론트엔드 Javascript 라이브러리이다. 그리고 Meta는 React에 대한 업데이트를 자주 해준다고 한다. 즉, React를 사용하는 웹애플리케이션들은 Meta의 업데이트에 영향을 많이 받는다는 뜻이다.
React를 사용하는 이유란 뭘까에 대해 생각해보고 여러가지 자료들을 찾아보았다. 예전에는 웹사이트를 제작할 때 JavaScript, CSS, HTML을 사용해서 페이지를 주로 제작을 많이 했는데, 규모가 커지면 이 코드들이 너무 복잡해지고 유지보수가 어려워지기 때문에 만들어졌다고 한다.
컴포넌트(Component) 기반 구조
React는 컴포넌트 기반의 UI 라이브러리이다. 이 말이 처음에는 너무 어렵게 느껴졌는데 쉽게 설명하자면 "React는 모든 걸 컴포넌트로 쪼개서 개발한다." 라는 의미이다. 즉, 컴포넌트는 재사용 가능한 UI 조각이라고 생각하면 된다.
예제를 보면
function Button() {
return <button className="bg-blue-500 text-white p-2">클릭!</button>;
}
위 버튼을 함수처럼 다른 곳에서 Button
으로 사용 가능하다. 이렇게 컴포넌트로 제작하게 되면 재사용하기 편리할 뿐만 아니라 유지보수 측면에서도 분명한 강점을 갖고 있는 것이다.
나는 이 부분이 가장 큰 장점이라고 생각한다. 사실 우리가 코딩을 하면서도 매번 함수를 작은 단위까지 쪼개 나가는 이유가 유지보수 측면에서 매우 큰 작용을 하기 때문이다. 겉으로 보아서는 큰 차이가 없는 것처럼 보이지만, 실제로 함수화한다면 어떠한 문제가 생겼을 때, 이에 대한 대처가 유용하게 가능하다.
이러한 개념이 React에도 적용되어있다는 것을 보고 더 매력을 느낀 것도 있다. 게다가 규모가 커지면서 코드가 복잡해지는 것도 경험해봤던 터라, 공감이 안될 수 없었다.
가상 DOM
또한 React는 실제 DOM을 직접 사용하지 않고, 가상 DOM을 사용해 성능을 최적화한다.
여기에서 DOM이란 무엇인지 의문을 가질 수 있는데 쉽게 생각해 웹페이지의 구조를 트리(Tree) 형태로 표현한 것이라고 생각하면 편하다.
React는 가상 DOM을 사용한다고 했는데, 일반적인 웹사이트들은 HTML을 변경할 때마다 브라우저가 전체 페이지를 다시 그려야해서 속도가 느리다.
하지만 React를 사용하게 되면 변경된 HTML 부분만 업데이트를 하기 때문에 속도가 상대적으로 빠를 수 밖에 없다.
웹사이트를 이용하다보면 가끔 로딩이 느릴 때가 종종 있다. 나는 이에 의문을 갖곤 했다. 어떤 사이트는 로딩이 무척 빠른 반면, 어떤 사이트는 로딩이 매우매우 느린 이유가 뭘까?
하지만 지금 생각해보니 그건 기술력 차이일 수도 있겠다 생각이 든다. 물론 React뿐만 아니라 여러가지 복합적인 요소가 적용되어 결과적으로 그 속도면에서 차이가 나는 것일테지만, 지금 당장은 쉽게 생각해 이러한 사소한 차이들이 쌓이고 쌓여 표면적으로 보이는 그런 결과를 나타낸다는 것을 알게 되었다.
상태 관리
React는 상태(state)를 통해 화면을 동적으로 관리한다.
사용자가 버튼을 클릭하거나 데이터를 입력하면 화면이 바뀌어야하는데, React는 이를 useState()
훅을 이용해서 동적인 데이터를 관리할 수 있다고 한다.
import React, { useState } from "react";
function Counter() {
const [count, setCount] = useState(0); // 상태 선언
return (
<div>
<p>현재 카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>+1 증가</button>
</div>
);
}
여기 예시 코드가 있다. 위를 실행하면 버튼을 클릭할 때마다 화면이 자동으로 업데이트된다.
이 또한 React에서 중요한 역할이라고 생각한다. 동적인 데이터를 관리함으로써 다른 모듈이 하는 역할이 줄어들기 때문에 이에 대한 이점이 분명히 있을 것이다.
JSX(JavaScript XML)
React는 JSX라는 문법을 사용한다.
const element = <h1>Hello, React! 🚀</h1>;
위 처럼 HTML 같기도하고 JavaScript 같기도 한 이 코드는 JSX 코드이다.
기존에는 document.createElement()
같은 코드로 요소를 만들었다.
하지만 JSX를 사용하면 HTMl처럼 쉽게 작성이 가능하다.
결과적으로 HTML과 비슷하지만, JavaScript 문법이 포함된 JSX 덕분에 코드가 간결해진다.
React를 사용할까?
과거의 내가 위의 정리한 내용들을 본다면 React를 사용할 수 밖에 없을 것 같다.
그리고 현재의 나는 위에서 언급한대로 현재는 React를 사용하고 있다.
물론 지금은 React에 대한 지식이 많이 부족하지만, 이는 앞으로 천천히 쌓아나가야하는 부분이다.
리액트(React) 사용
1. React 프로젝트 생성
그렇다면 React 프로젝트를 생성해보자.
나는 현재 프로젝트를 backend와 frontend로 나누어서 관리하고 있다.
이렇게 관리하면 Git에 Commit할 때 각각의 레포지토리로 나누어 관리하기 편리할 것 같았기 때문이다.
그리고 전체적인 코드의 유지보수 측면에서도 가독성이 좋을 수 밖에 없을 것 같았다.
그래서 frontend 폴더 안에서 작업을 시작할 것이다.
npx create-react-app frontend
cd frontend
npm start
기본 React 프로젝트가 생성되고, localhost:3000에서 실행된다.
2. React Router 설정 (페이지)
페이지 구성은 우선CarList.js
(차량 목록 페이지), CarDetail.js
(차량 상세 페이지)
이렇게 두 개만 만들었다. 물론 앞으로 더 많은 페이지가 추가될 수도 있다.
설치
npm install react-router-dom
App.js (라우터 적용)
import { Routes, Route } from "react-router-dom";
import CarList from "./pages/CarList";
import CarDetail from "./pages/CarDetail";
function App() {
return (
<div className="bg-gray-100 min-h-screen p-10">
<h1 className="text-4xl font-bold text-center text-blue-700 mb-6">
AutoFinder - 차량 검색 서비스
</h1>
<Routes>
<Route path="/" element={<CarList />} />
<Route path="/cars/:id" element={<CarDetail />} />
</Routes>
</div>
);
}
export default App;
- / → 차량 목록 페이지
- /cars/:id → 차량 상세 페이지
각 url에 매핑된다고 보면 된다.
3. API 연동 (백엔드 데이터 가져오기)
설치
npm install axios
CarList.js
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
const CarList = () => {
const [cars, setCars] = useState([]);
useEffect(() => {
axios.get("/api/cars")
.then(response => {
console.log("차량 데이터 응답:", response.data);
setCars(response.data.content || []);
})
.catch(error => {
console.error("차량 목록을 가져오는 중 오류 발생:", error);
});
}, []);
return (
<div>
<h2>차량 목록</h2>
{cars.length > 0 ? (
<ul>
{cars.map(car => (
<li key={car.id}>
{car.model} - {car.year} - {car.price?.toLocaleString() ?? "정보 없음"} 원
<br />
<Link to={`/cars/${car.id}`}>상세보기</Link>
</li>
))}
</ul>
) : (
<p>불러올 차량이 없습니다.</p>
)}
</div>
);
};
export default CarList;
백엔드에서 /api/cars
데이터를 가져와서 목록을 표시한다.
각 차량을 클릭하면 상세 페이지로 이동하는 기능을 갖고 있다.
Tailwind?
여기까지만 하면 사실 기본적인 구조만 갖춘 웹사이트가 만들어지게 된다. 하지만 나는 테스트를 수도없이 반복할 것이기 때문에 웹사이트의 페이지가 조금 이뻤으면 좋겠다고 생각했다.
그래서 바로 CSS를 적용하려고 했으나, 단순히 css를 적용하는 것보다 더 좋은 방법이 있지 않을까 찾아보게 되었다.
바로 Tailwind라는 CSS 프레임워크를 사용하는 것이다.
Tailwind CSS는 유틸리티-퍼스트(Utillity-First) CSS 프레임워크이다. 즉, 쉽게 말해 미리 정의된 클래스를 조합해서 보다 빠르고 쉽게 스타일을 적용할 수 있는 도구이다.
일반 CSS 방식
/* styles.css */
.button {
background-color: blue;
color: white;
padding: 10px 20px;
border-radius: 5px;
}
<button class="button">클릭</button>
일반 CSS 방식은 CSS 파일을 따로 작성하고, 클래스 이름을 관리해야한다.
반면 Tailwind를 사용한다면 더욱 간편해진다.
Tailwind 방식
<button class="bg-blue-500 text-white px-4 py-2 rounded">클릭</button>
이렇게 한 줄로 끝난다.
CSS 파일이 필요 없고, 클래스를 조합해서 원하는 디자인을 바로 만들 수 있다.
그래서 Tailwind를 사용하기로 결정했다.
Tailwind 사용
설치
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
tailwind.config.js 수정
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
}
@tailwind base;
@tailwind components;
@tailwind utilities;
결과적으로 TailwindCSS가 적용되어 깔끔한 디자인으로 변경된다.
Tailwind를 적용한 전체 코드
CarDetail.js
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';
const CarDetail = () => {
const { id } = useParams();
const [car, setCar] = useState(null);
useEffect(() => {
axios.get(`/api/cars/${id}`)
.then(response => {
console.log("차량 상세 정보:", response.data);
setCar(response.data);
})
.catch(error => {
console.error("차량 정보를 가져오는 중 오류 발생:", error);
});
}, [id]);
if (!car) return <p className="text-center text-gray-600">차량 정보를 불러오는 중...</p>;
return (
<div className="max-w-4xl mx-auto p-8 bg-white shadow-md rounded-lg mt-10">
<h1 className="text-3xl font-bold text-gray-800">{car.model}</h1>
<p className="text-gray-500">연식: {car.year} | 주행거리: {car.mileage?.toLocaleString() ?? "정보 없음"} km</p>
<p className="text-gray-600 mt-2">연료 타입: {car.fuel}</p>
<p className="text-gray-600">지역: {car.region}</p>
<p className="text-2xl font-bold text-blue-600 mt-4">{car.price?.toLocaleString() ?? "정보 없음"} 만원</p>
<a href={car.url} target="_blank" rel="noopener noreferrer"
className="mt-5 block text-center bg-green-500 text-white py-2 rounded-md hover:bg-green-600 transition">
상세 정보 보기
</a>
</div>
);
};
export default CarDetail;
CarList.js
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
const CarList = () => {
const [cars, setCars] = useState([]);
useEffect(() => {
axios.get("/api/cars")
.then(response => {
console.log("차량 데이터 응답:", response.data);
setCars(response.data.content || []);
})
.catch(error => {
console.error("차량 목록을 가져오는 중 오류 발생:", error);
});
}, []);
return (
<div className="container mx-auto p-8">
<h2 className="text-4xl font-bold text-center text-blue-600 mb-6">
차량 목록
</h2>
{cars.length > 0 ? (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{cars.map(car => (
<div key={car.id} className="bg-white shadow-lg rounded-lg p-5 transition transform hover:scale-105 hover:shadow-xl">
<h3 className="text-xl font-semibold text-gray-900">{car.model}</h3>
<p className="text-gray-600">{car.year} | {car.fuel}</p>
<p className="text-gray-800 font-bold mt-2">{car.price?.toLocaleString() ?? "정보 없음"} 만원</p>
<p className="text-sm text-gray-500">{car.region}</p>
<Link to={`/cars/${car.id}`} className="mt-4 block text-center bg-blue-500 text-white py-2 rounded-md hover:bg-blue-600 transition">
상세보기
</Link>
</div>
))}
</div>
) : (
<p className="text-center text-gray-600">불러올 차량이 없습니다.</p>
)}
</div>
);
};
export default CarList;
결론
사실 며칠전까지만해도, 백엔드의 기능들을 다루고 있었고, API 테스트를 제외하면 눈에 띄는 결과가 나타나지 않아 내가 잘 하고 있는 건지에 대해 의구심이 들었다. 그래서 프론트엔드 연동을 최대한 빨리 해보고 싶었다. 연동을 하면 테스트하기 편해질 뿐만 아니라 내가 지금까지 해온 작업들이 눈에 보여서 더 자신감이 붙을 것 같았다.
그래서 React와 Tailwind를 접하면서 직접 사용하고 다루게 되었다. 사실 프론트엔드는 나에게 매우 어려운 과제 같은 느낌이 크다. 물론 백엔드 개발자가 되고 싶은 나는 백엔드에 대해 더 전문적으로 알아야하지만, 프론트엔드는 깊게 생각해본 적이 없었기 때문이다.
그렇지만 여기에서 프론트엔드까지 내가 접한다면 풀스택 개발자에 어떻게 보면 한 발짝 더 가까워지는 부분이라 시도한 것 같다.
완전히 풀스택 개발자가 되지 않더라도 안정적인 백엔드 개발자가 되고 싶은데, 아무리 백엔드 개발자라고 하더라도 프론트엔드와 협업을 해야할텐데, 그에 대한 지식이 아무것도 없으면 안된다고 생각했다. 아직 실무 경험이 없어서 잘 모르는 부분이긴 하지만 이건 지극히 내 생각? 추측?이다. 최소한의 지식을 갖추고 프론트엔드쪽과 협업을 하는 것이 중요하다고 생각했고, 사실 지식은 갖추면 갖출 수록 좋은 것이기 때문에 계속 공부를 해나가야할 것 같다.
백엔드 개발자가 되고 싶은 내 희망? 꿈?이 실현될 수 있도록 계속 코드를 짜고 공부를 해 나가면서 부족한 부분은 유연하게 매꾸고 싶다.
'SIDE PROJECT > AUTOFINDER' 카테고리의 다른 글
회원가입, 로그인 로직 보완 및 보안 강화 (0) | 2025.04.17 |
---|---|
차량의 필터링(조건 검색) 기능 추가 (1) | 2025.04.17 |
백엔드 코드 개선 및 쿼리문 수정 (0) | 2025.04.17 |
JWT를 활용한 로그인 및 API 인증 처리 (0) | 2025.04.17 |
회원가입, 보안 로직 개발 (1) | 2025.04.17 |