diff --git a/01-first-component/README.md b/01-first-component/README.md
deleted file mode 100644
index 72b1469..0000000
--- a/01-first-component/README.md
+++ /dev/null
@@ -1,46 +0,0 @@
-# 01. 컴포넌트 선언하고 사용하기: Component 기본 구조와 JSX
-
-## 🎯 요구 사항
-- `/templates` 에 있는 html 템플릿을 그대로 `App.jsx`에서 그릴 수 있게 해보세요.
-- `App.jsx`를 여러 개의 컴포넌트로 분리해서 그려보세요.
- - 스타일도 별도의 css파일로 분리하여 각 컴포넌트에서 import합니다.
- - (선택) `module.css` 를 사용해 보세요.
-
-### 구현 결과 예시
-- 예를 들어, `App.jsx`의 return문을 아래와 같이 작성했을 때에 앱이 정상적으로 그려지도록 구현해 주세요.
-- 컴포넌트의 이름이나 구조는 마음대로 변경해도 좋습니다
-```javascript
-function App() {
- return (
- <>
-
-
-
-
-
-
- >
- );
-}
-```
-
-## ✅ 키워드
-- JSX
- - `class` -> `className`
- - `for` -> `htmlFor`
- - self closing tag
- - Fragment
- - `{}` 내에 쓸 수 있는 JS 식
-- React Component
- - 기본 구조
- - export / import
-
-## 🧙♀️ 진행 가이드
-- 진행 시간: 1시간 내에 완료하는 것을 목표로 합니다.
-
-## 🔗 참고 문서
-- [Thinking in React](https://react.dev/learn/thinking-in-react)의 Step1-2 에 있는 것처럼 나만의 컴포넌트 단위를 나누어 보세요.
-- [Your First Component](https://react.dev/learn/your-first-component)
diff --git a/01-styled-components/README.md b/01-styled-components/README.md
new file mode 100644
index 0000000..14979e9
--- /dev/null
+++ b/01-styled-components/README.md
@@ -0,0 +1,85 @@
+# 01. styled-components를 적용해서 리팩토링하기
+
+## 🎯 요구사항
+- 초록스터디-`self-paced-react`의 step5 코드를 가져와 스타일링하는 미션입니다.
+- styled-components 라이브러리를 이용해서 컴포넌트들에게 css를 입히고 스타일링 해보세요.
+- styled-components를 왜 사용하는지, 별도의 css파일로 분리한 방법과 어떤 trade-off가 있는지 PR에 적어주세요.
+ - (선택) 브라우저가 웹 페이지를 렌더링하는 구조와 과정에 대해서도 공부해보세요.
+ - **🔑keywords** : DOM Tree, CSSOM Tree, Render Tree
+- 프로젝트내 별도의 css파일은 존재하지 않아야합니다.❌
+ - App.css는 허용되며 아래의 코드와 동일해야합니다.
+```css
+* {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+ }
+
+ul,
+li {
+list-style: none;
+}
+
+html,
+body {
+font-family: sans-serif;
+font-size: 16px;
+}
+
+/* Colors *****************************************/
+:root {
+--primary-color: #ec4a0a;
+--lighten-color: #f6a88a;
+--grey-100: #ffffff;
+--grey-200: #d0d5dd;
+--grey-300: #667085;
+--grey-400: #344054;
+--grey-500: #000000;
+}
+
+```
+
+### 😗구현 예시
+- 컴포넌트의 이름이나 구조는 마음대로 변경해도 좋습니다.
+```javascript
+import React from 'react';
+import styled from 'styled-components';
+
+// Button 컴포넌트를 styled-components로 정의
+const Button = styled.button`
+ background-color: #ec4a0a;
+ color: white;
+ border: none;
+ border-radius: 8px;
+ padding: 10px 20px;
+ font-size: 16px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #f6a88a;
+ }
+`;
+
+const App = () => {
+ return (
+
+
Hello, styled-components!
+
+
+ );
+};
+
+export default App;
+```
+
+## ✅ 키워드
+- **styled-component**
+ - Css in JS
+ - Scoped Styling
+
+## 🧙♀️ 진행 가이드
+- 진행시간 : 1시간 내에 완료하는 것을 목표로 합니다.
+- vscode 사용시 extension에서 `vscode-styled-components`를 설치해주세요.
+
+## 🔗 참고 문서
+- [styled-components 공식문서](https://styled-components.com/docs)
\ No newline at end of file
diff --git a/02-rendering-lists/README.md b/02-rendering-lists/README.md
deleted file mode 100644
index 3fcfcf1..0000000
--- a/02-rendering-lists/README.md
+++ /dev/null
@@ -1,74 +0,0 @@
-# 02. 목록 UI 구현하기: Props와 State
-
-## 🎯 요구 사항
-- `RestaurantList` 가 restaurants 배열을 받아서 그릴 수 있도록 변경해 보세요.
- - restaurants 배열을 `RestaurantList` 의 props로 내려받도록 변경해 보세요.
-- 카테고리 필터에 따라 필터된 음식점 목록을 보여줄 수 있도록 변경해 보세요.
-
-### 구현 결과 예시
-```javascript
-// App.jsx
-
-
-```
-```javascript
-const restaurants = [
- {
- id: "a01",
- name: "피양콩할마니",
- description:
- "평양 출신의 할머니가 수십 년간 운영해온 비지 전문점 피양콩 할마니. 두부를 빼지 않은 되비지를 맛볼 수 있는 곳으로, ‘피양’은 평안도 사투리로 ‘평양’을 의미한다. 딸과 함께 운영하는 이곳에선 맷돌로 직접 간 콩만을 사용하며, 일체의 조미료를 넣지 않은 건강식을 선보인다. 콩비지와 피양 만두가 이곳의 대표 메뉴지만, 할머니가 옛날 방식을 고수하며 만들어내는 비지전골 또한 이 집의 역사를 느낄 수 있는 특별한 메뉴다. 반찬은 손님들이 먹고 싶은 만큼 덜어 먹을 수 있게 준비돼 있다.",
- category: "한식",
- },
- {
- id: "a02",
- name: "친친",
- description: "Since 2004 편리한 교통과 주차, 그리고 관록만큼 깊은 맛과 정성으로 정통 중식의 세계를 펼쳐갑니다",
- category: "중식",
- },
- {
- id: "a03",
- name: "잇쇼우",
- description:
- "잇쇼우는 정통 자가제면 사누끼 우동이 대표메뉴입니다. 기술은 정성을 이길 수 없다는 신념으로 모든 음식에 최선을 다하는 잇쇼우는 고객 한분 한분께 최선을 다하겠습니다",
- category: "일식",
- },
- {
- id: "a04",
- name: "이태리키친",
- description: "늘 변화를 추구하는 이태리키친입니다.",
- category: "양식",
- },
- {
- id: "a05",
- name: "호아빈 삼성점",
- description: "푸짐한 양에 국물이 일품인 쌀국수",
- category: "아시안",
- },
- {
- id: "a06",
- name: "도스타코스 선릉점",
- description: "멕시칸 캐주얼 그릴",
- category: "기타",
- },
-];
-```
-
-
-## ✅ 키워드
-- Props
-- State
- - useState
-- Keys
-
-> [Rendering Lists](https://react.dev/learn/rendering-lists) 문서에 ['Why does React need keys?'](https://react.dev/learn/rendering-lists#why-does-react-need-keys)는 지금 꼭 이해하지 않아도 괜찮습니다. 그냥 React에서 목록을 동적으로 그릴 때에는 이런 것들을 사용해야 하는구나~ 정도로만 알고 일단 넘어가세요. 우선 사용하는 법에 익숙해지는 것이 먼저입니다 :)
-
-## 🧙♀️ 진행 가이드
-- 진행 시간: 1시간 내에 완료하는 것을 목표로 합니다.
-
-## 🔗 참고 문서
-- [Thinking in React](https://react.dev/learn/thinking-in-react)의 Step3-4
-- [Passing Props to a Component](https://react.dev/learn/passing-props-to-a-component)
-- [Rendering Lists](https://react.dev/learn/rendering-lists)
-- [State: A Component's Memory](https://react.dev/learn/state-a-components-memory)
- - [API Reference: useState](https://react.dev/reference/react/useState)
\ No newline at end of file
diff --git a/03-modal/README.md b/03-modal/README.md
deleted file mode 100644
index bc74460..0000000
--- a/03-modal/README.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# 03. 모달 UI 구현하기: side-effect(feat. event handler)
-
-## 🎯 요구 사항
-- `RestaurantList` 의 아이템을 클릭하면, 클릭한 아이템의 정보를 보여주는 모달이 뜨도록 변경해 주세요. '확인' 버튼을 클릭하거나 모달 뒤의 backdrop을 클릭하면 모달이 닫혀야 합니다.
- - (작은 단계로 구현해보기 1) 아이템을 클릭하면 정해진 텍스트를 그대로 보여주는 모달을 열고 닫습니다.
- - (작은 단계로 구현해보기 2) 클릭한 아이템의 정보를 모달에 내려줄 수 있도록 개선합니다.
-
-### 구현 결과 예시
-```javascript
-// App.jsx
-{isModalOpen && }
-```
-
-## ✅ 키워드
-- event handler (feat. side effect)
-- conditional rendering
-- lifting state up
-
-## 🧙♀️ 진행 가이드
-- 진행 시간: 1시간 내에 완료하는 것을 목표로 합니다.
-
-## 🔗 참고 문서
-- [Thinking in React](https://react.dev/learn/thinking-in-react)의 Step5
-- [Responding to Events](https://react.dev/learn/responding-to-events)
\ No newline at end of file
diff --git a/04-form/README.md b/04-form/README.md
deleted file mode 100644
index b029ec9..0000000
--- a/04-form/README.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# 04. 폼 UI 구현하기: controlled vs uncontrolled
-
-## 🎯 요구 사항
-- `Header`의 레스토랑 추가 버튼을 클릭하면 레스토랑 추가 폼이 모달로 뜨도록 구현해 주세요
- - 이전 단계에서 만들어두었던 `AddRestaurantModal`을 그대로 사용합니다.
-- 카테고리를 선택하고, ``, ``에 값을 입력한 뒤 '추가하기' 버튼을 클릭하면 레스토랑 목록에 추가되도록 구현해 주세요.
- - 유효성 검사는 하지 않습니다. 아주 간단하게 입력값을 처리해보는 구현만 해도 충분합니다.
- - id는 `Date.now()`값을 임의로 할당합니다.
-- (optional) 재사용할 수 있는 Modal 컴포넌트를 만들어서 `AddRestaurantModal`, `RestaurantDetailModal`을 Modal 컴포넌트를 활용해 구현하는 것으로 개선해 보세요
-
-## ✅ 키워드
-- controlled vs uncontrolled
-- children props
-
-> 재사용할 수 있는 모달을 만들 때 `children`을 활용해 보세요. 아래와 같은 식으로 UI를 구성할 수 있습니다.
-
-```javascript
-// 설명을 위한 예시용 마크업입니다. 실제로 사용하는 마크업은 template/ 하위의 html을 참고하거나 직접 원하는대로 구현하여 사용해 주세요.
-// 반드시 아래와 같은 형식으로 쓸 필요는 없습니다. 원하는 방식대로 재사용 가능한 컴포넌트를 만들어 보세요.
-
-// AddRestaurantModal.jsx
-
-
-
-
-// RestaurantDetailModal.jsx
-
-
-
-
-```
-
-## 🧙♀️ 진행 가이드
-- 진행 시간: (optional 제외) 2시간 내에 완료하는 것을 목표로 합니다.
-
-## 🔗 참고 문서
-- [Sharing State Between Components](https://react.dev/learn/sharing-state-between-components)
-- [API Reference: ](https://react.dev/reference/react-dom/components/input)
\ No newline at end of file
diff --git a/05-effects/README.md b/05-effects/README.md
deleted file mode 100644
index 3c445ae..0000000
--- a/05-effects/README.md
+++ /dev/null
@@ -1,49 +0,0 @@
-# 05. API 연동하기: side-effect(feat. effects)
-
-## 🎯 요구 사항
-- API로 레스토랑 목록을 불러와 ``에 내려줍니다.
- - 로딩 상태, 에러 상태 등은 고려하지 않습니다.
-- 레스토랑 추가 모달에서 추가하기 버튼을 클릭하면 POST 요청을 보냅니다. 모달이 닫히고, 레스토랑 목록을 다시 불러옵니다.
-
-## ✅ 키워드
-- effect (feat. side effect)
- - useEffect
-
-## 🧙♀️ 진행 가이드
-
-- 진행 시간: 2시간 내에 완료하는 것을 목표로 합니다.
-
-### `json-server`로 가짜 서버 띄워 활용하기
-연습용 앱이기 때문에 [`json-server`](https://github.com/typicode/json-server)를 활용해 간단한 가짜 REST API를 구축해 사용합니다.
-- `npm run server`를 실행합니다. (혹은 `npx json-server db.json` 를 직접 실행해도 상관없습니다)
-- `GET http://localhost:3000/restaurants`으로 `db.json`에 있는 레스토랑 목록을 불러올 수 있습니다.
-```javascript
-// GET 예시
-const response = await fetch("http://localhost:3000/restaurants");
-
-// POST 예시
-const response = await fetch("http://localhost:3000/restaurants", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify(restaurant),
-});
-```
-
-## 🔗 참고 문서
-- [Synchronizing with Effects](https://react.dev/learn/synchronizing-with-effects)
-> Effects let you specify side effects that are caused by rendering itself, rather than by a particular event.
- - [API Reference: useEffect](https://react.dev/reference/react/useEffect)
- > useEffect is a React Hook that lets you synchronize a component with an external system.
-- [API Reference: useSate > updater function](https://react.dev/reference/react/useState#updating-state-based-on-the-previous-state)
- > `set` function에 함수를 넘겨주면 `updater function`으로 동작합니다. 함수가 아닌 값을 넘겨줄 때와 어떻게 다른지 알아보세요.
-
-구현을 다 해본 뒤에 Introduction에서 살펴보았던 설계 원칙과 관련해 조금 더 학습해보고 싶다면 아래 문서들도 추가로 확인해 보세요.
-- [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)
-> You do need Effects to synchronize with external systems.
-> In React, data flows from the parent components to their children.
-- [Components and Hooks must be pure](https://react.dev/reference/rules/components-and-hooks-must-be-pure)
-> Purity in Components and Hooks is a key rule of React that makes your app predictable, easy to debug, and allows React to automatically optimize your code.
-> Side effects should not run in render, as React can render components multiple times to create the best possible user experience.
-> One important principle in React is local reasoning: the ability to understand what a component or hook does by looking at its code in isolation. Hooks should be treated like “black boxes” when they are called.
\ No newline at end of file
diff --git a/README.md b/README.md
index e9671f0..357e2a0 100644
--- a/README.md
+++ b/README.md
@@ -1,387 +1,218 @@
-# API 요청과 비동기 처리
+# styled-components를 적용해서 리팩토링하기
## 🎯 개인 목표 및 목표 달성을 위한 행동 가이드
이번 미션을 통해 다음과 같은 학습 경험들을 쌓는 것을 목표로 한다.
-1. side effect가 무엇인지 이해하고, `useEffect`가 왜 필요한지 설명할 수 있다.
-2. Promise가 무엇인지 이해하고, async/await가 Promise를 어떻게 다루는지 설명할 수 있다.
-3. `fetch`로 GET/POST 요청을 보내고 응답을 처리하는 방법을 익히고, `await`를 어디에 붙여야 하는지 스스로 판단할 수 있다.
+1. CSS Modules 방식과 CSS-in-JS 방식의 차이를 직접 마이그레이션하며 체감한다.
+2. styled-components의 기본 사용법(기본 스타일링, 중첩 선택자, 컴포넌트 확장)을 익힌다.
+3. 기존 코드의 구조를 유지하면서 스타일링 방식만 교체하는 리팩토링 경험을 쌓는다.
---
## 📝 기능 구현 목록
-- [x] API로 음식점 목록을 불러와 RestaurantList에 렌더링
-- [x] 음식점 추가 시 POST 요청 후 목록 재조회
+- [x] 모든 `.module.css` 파일을 제거하고 styled-components로 전환
+- [x] `Header`, `CategoryFilter`, `Modal`, `RestaurantList`, `RestaurantDetailModal`, `AddRestaurantModal` 전 컴포넌트에 styled-components 적용
+- [x] `App.css`는 전역 리셋 및 CSS 변수 정의 용도로만 유지
+- [x] CSS 변수(`var(--primary-color)` 등)를 styled-components 내부에서 활용
+- [x] `styled(Component)` 확장 패턴을 활용해 필수 입력 항목(`RequiredFormItem`) 스타일 분리
---
## 📚 학습 내용
-### useEffect와 side effect
+### styled-components란?
-React 컴포넌트는 렌더링 중에 외부 시스템(서버, 타이머, DOM 직접 조작 등)에 영향을 주면 안 된다. 이런 작업을 side effect라고 하고, `useEffect`는 렌더링이 끝난 뒤 이를 안전하게 실행하는 공간이다.
+CSS-in-JS 라이브러리로, JavaScript 파일 안에서 템플릿 리터럴 문법으로 CSS를 작성하고 이를 React 컴포넌트에 직접 연결하는 방식이다.
-```jsx
-useEffect(() => {
- // 렌더링 이후 실행 — 서버 요청, 구독, DOM 조작 등
-}, []);
-```
-
-의존성 배열 `[]`를 넘기면 컴포넌트가 처음 마운트될 때 한 번만 실행된다. 배열을 아예 생략하면 매 렌더링마다 실행되어 무한 루프가 생길 수 있다.
-
-### useEffect 안에서 async/await 쓰는 법
-
-`useEffect`의 콜백은 직접 `async`로 만들 수 없다. `async` 함수는 항상 Promise를 반환하는데, React는 `useEffect` 콜백의 반환값을 cleanup 함수로 기대하기 때문이다.
-
-```jsx
-// ❌ 이렇게 하면 안 됨 — async 콜백이 Promise를 반환해서 React가 경고
-useEffect(async () => {
- const data = await getRestaurants();
- setRestaurants(data);
-}, []);
-
-// ✅ 내부에서 async 함수를 정의하고 호출
-useEffect(() => {
- const fetchRestaurants = async () => {
- const data = await getRestaurants();
- setRestaurants(data);
- };
- fetchRestaurants();
-}, []);
-```
-
-### async/await를 어디에 붙여야 하는가
-
-`await`는 Promise를 반환하는 함수 앞에 붙인다. `await`를 쓰는 함수 자신은 반드시 `async`여야 한다. 이 규칙이 호출 체인을 따라 전파된다.
-
-```
-fetch() → Promise 반환
-getRestaurants() → 내부에서 await fetch() → async 필요
-handleRestaurantSubmit() → 내부에서 await getRestaurants() → async 필요
-```
-
-실수하기 쉬운 패턴: `async` 함수를 호출할 때 `await`를 빠뜨리면 Promise가 풀리기 전에 다음 줄이 실행된다.
-
-```jsx
-// ❌ await 누락 — POST가 완료되기 전에 GET 실행, 새 항목이 목록에 없을 수 있음
-async function handleRestaurantSubmit(restaurant) {
- addRestaurant(restaurant); // await 없음
- const data = await getRestaurants();
- setRestaurants(data);
-}
-
-// ✅ POST 완료를 기다린 뒤 GET
-async function handleRestaurantSubmit(restaurant) {
- await addRestaurant(restaurant);
- const data = await getRestaurants();
- setRestaurants(data);
-}
+```js
+const Button = styled.button`
+ background-color: var(--primary-color);
+ color: white;
+ border-radius: 8px;
+ padding: 10px 20px;
+`;
```
-### api.js 파일 위치
+### CSS Modules vs styled-components trade-off
-현재 `src/api.js`에 두었다. 프로젝트 규모에 따라 관례가 다르다.
-
-| 규모 | 구조 | 예시 |
+| | CSS Modules | styled-components |
|---|---|---|
-| 소규모 | 단일 파일 | `src/api.js` |
-| 중규모 | 도메인별 분리 | `src/api/restaurants.js`, `src/api/users.js` |
-| 대규모 | services 레이어 | `src/services/restaurantService.js` |
+| 스타일 위치 | 별도 `.module.css` 파일 | 컴포넌트 파일 내부 |
+| 스코프 | 클래스명 해시로 자동 격리 | 컴포넌트 단위로 격리 |
+| 동적 스타일링 | className 조건부 변경 필요 | props로 직접 처리 가능 |
+| 가독성 | HTML 구조와 스타일 파일 분리 | 한 파일에서 구조+스타일 파악 가능 |
+| 번들 크기 | 별도 런타임 없음 | styled-components 런타임 포함 |
-`api/`와 `services/`의 차이는 뉘앙스 차이다. `api/`는 서버와의 통신 함수 모음이라는 의미가 강하고, `services/`는 비즈니스 로직까지 포함할 수 있다는 의미로 쓰이기도 한다. 팀마다 다르므로 프로젝트 컨벤션을 따르면 된다. 현재 프로젝트처럼 API 함수가 몇 개 없을 때는 `src/api.js` 하나로 충분하다.
+### 자식 선택자 중첩
----
+styled-components는 Sass처럼 중첩 선택자를 지원한다.
-## 🤔 고민했던 문제와 해결 과정에서 배운 점
-
-### async 함수를 정의만 하고 호출하지 않은 문제
-
-`useEffect` 안에서 async 함수를 정의했지만 호출하지 않아 API 요청이 실행되지 않았다. 같은 실수를 두 번 했다.
+```js
+const Category = styled.div`
+ background: var(--lighten-color);
-```jsx
-// ❌ 정의만 하고 호출하지 않음 — 아무 일도 일어나지 않음
-useEffect(() => {
- async () => {
- const data = await getRestaurants();
- setRestaurants(data);
- };
-}, []);
-
-// ✅ 정의 후 호출
-useEffect(() => {
- const fetchRestaurants = async () => {
- const data = await getRestaurants();
- setRestaurants(data);
- };
- fetchRestaurants(); // 호출
-}, []);
+ img {
+ width: 36px;
+ height: 36px;
+ }
+`;
```
-### 커스텀 훅 분리 — `useRestaurants`
-
-`App`이 렌더링 구조와 UI 상태를 다루는 컴포넌트인데, 서버 통신과 데이터 관리 로직까지 함께 들어있어 역할이 섞여 있었다. `restaurants` state, `useEffect`, fetch 로직을 커스텀 훅으로 추출했다.
-
-### `fetchRestaurants`를 `useCallback`으로 추출
-
-`fetchRestaurants`가 `useEffect` 내부에 정의되어 있어 `addRestaurant`에서 같은 로직을 중복으로 작성해야 했다. 함수를 훅 스코프로 꺼내 재사용하려면 `useCallback`이 필요하다.
+### 컴포넌트 확장 (`styled(Component)`)
-`useCallback` 없이 일반 함수로 선언하면 렌더링마다 새 함수 참조가 생성되어 `useEffect([fetchRestaurants])`가 매 렌더링마다 실행되는 무한 루프가 발생한다. `useCallback`의 빈 의존성 배열 `[]`이 참조를 고정해 이를 방지한다.
+기존 styled-component를 상속해서 스타일을 추가할 수 있다.
```js
-// Before: fetchRestaurants가 useEffect 안에 갇혀 있어 addRestaurant에서 로직을 중복 작성
-export function useRestaurants() {
- const [restaurants, setRestaurants] = useState([]);
-
- useEffect(() => {
- const fetchRestaurants = async () => {
- const data = await getRestaurants();
- setRestaurants(data);
- };
- fetchRestaurants();
- }, []);
-
- async function addRestaurant(restaurant) {
- await createRestaurant(restaurant);
- const data = await getRestaurants(); // 중복
- setRestaurants(data); // 중복
- }
-
- return { restaurants, addRestaurant };
-}
-
-// After: useCallback으로 추출해 재사용, 의존성 배열도 명시적으로 선언
-export function useRestaurants() {
- const [restaurants, setRestaurants] = useState([]);
-
- const fetchRestaurants = useCallback(async () => {
- const data = await getRestaurants();
- setRestaurants(data);
- }, []);
-
- useEffect(() => {
- void fetchRestaurants();
- }, [fetchRestaurants]);
+const FormItem = styled.div`...`;
- async function addRestaurant(restaurant) {
- await createRestaurant(restaurant);
- await fetchRestaurants(); // 재사용
+const RequiredFormItem = styled(FormItem)`
+ label::after {
+ content: "*";
+ color: var(--primary-color);
}
-
- return { restaurants, addRestaurant };
-}
+`;
```
-App에서는 서버 통신 로직이 모두 사라지고, 모달 열림/닫힘 같은 UI 상태만 남았다.
+---
-```jsx
-// Before: App이 서버 통신까지 담당
-const [restaurants, setRestaurants] = useState([]);
-useEffect(() => { /* fetch 로직 */ }, []);
-async function handleRestaurantSubmit(...) {
- await postRestaurant(...);
- const data = await getRestaurants();
- setRestaurants(data);
-}
-
-// After: 훅이 데이터 관리를 담당
-const { restaurants, addRestaurant } = useRestaurants();
-async function handleRestaurantSubmit(restaurant) {
- await addRestaurant(restaurant);
- setIsAddRestaurantModalOpen(false);
-}
-```
+## 🤔 고민했던 문제와 해결 과정에서 배운 점
-## 🛠 리팩토링
+### 필수/선택 폼 항목의 `*` 표시 처리
-### state 네이밍 — 용도가 아닌 값의 성격으로
+원본 CSS Modules에서는 `.formItem--required` 클래스를 조건부로 붙이는 방식으로 필수 항목에만 `*`를 표시했다.
-`filterCategory`는 이 state가 필터링에 쓰인다는 **용도**를 표현한다. state 이름은 어디에 쓰이는지가 아니라 **어떤 값을 담고 있는지**를 나타내는 게 좋다.
+styled-components로 전환할 때 `label::after { content: "*" }`를 `FormItem`에 바로 넣으면 모든 항목에 `*`가 붙는 문제가 생긴다.
-```jsx
-// Before: 용도 표현
-const [filterCategory, setFilterCategory] = useState("전체");
+`styled(FormItem)`으로 확장한 `RequiredFormItem`을 별도로 만들어서 필수 항목(카테고리, 이름)에만 적용하는 방식으로 해결했다.
-// After: 값의 성격 표현
-const [selectedCategory, setSelectedCategory] = useState("전체");
-```
+### 자식 선택자 중첩 — 언제 쓰고 언제 피할까
-### API 함수명 — HTTP 메서드가 아닌 의도로
+styled-components는 Sass처럼 중첩 선택자를 지원하지만, "항상 피해야 한다"기보다는 **상황에 따라 다르다**가 정확하다.
-`postRestaurant`는 HTTP 메서드 이름(`post`)을 그대로 쓴 것이다. 함수명은 어떻게 동작하는지가 아니라 무엇을 하려는지를 나타내야 한다.
+**중첩이 괜찮은 경우** — 부모와 항상 함께 쓰이는 단순 HTML 요소일 때는 별도 컴포넌트로 분리하는 게 오히려 과도한 추상화다.
```js
-// Before: 구현 방법 표현
-export async function postRestaurant(restaurant) { ... }
+// img는 Category 없이 독립적으로 쓰일 일이 없으므로 중첩이 합리적
+const Category = styled.div`
+ background: var(--lighten-color);
-// After: 의도 표현
-export async function addRestaurant(restaurant) { ... }
+ img {
+ width: 36px;
+ height: 36px;
+ }
+`;
```
-### BASE_URL 상수 추출
+`&:hover`, `&:focus` 같은 가상 선택자도 중첩이 자연스럽고 관용적인 방식이다.
-`http://localhost:3000`이 `getRestaurants`와 `addRestaurant` 두 곳에 반복됐다. 상수로 추출해 한 곳에서 관리한다.
+**중첩을 피하는 게 나은 경우** — 자식 요소가 의미 있는 스타일을 가지거나, 독립적으로 재사용될 가능성이 있을 때다.
```js
-const BASE_URL = "http://localhost:3000";
-```
-
-### try/catch 추가
+// h3/p는 각자 의미 있는 스타일을 가지므로 분리
+const RestaurantName = styled.h3`
+ font-size: 18px;
+ font-weight: 600;
+`;
-`fetch`는 네트워크 오류에서만 throw하고, HTTP 4xx/5xx 응답은 throw하지 않는다. 두 가지를 모두 처리하려면 `response.ok` 확인과 `try/catch`가 함께 필요하다.
-
-```js
-export async function getRestaurants() {
- try {
- const response = await fetch(`${BASE_URL}/restaurants`);
- if (!response.ok) throw new Error(`서버 오류: ${response.status}`);
- const restaurants = await response.json();
- return restaurants;
- } catch (error) {
- console.error("음식점 목록 조회 실패:", error);
- throw error; // 호출한 쪽이 에러를 알 수 있도록 다시 던짐
- }
-}
+const RestaurantDescription = styled.p`
+ padding-top: 8px;
+ -webkit-line-clamp: 2;
+`;
```
-catch에서 `throw error`를 다시 던지는 이유: 로그만 남기고 삼켜버리면 호출한 쪽(`App.jsx`)이 에러 발생 여부를 알 수 없다.
-
-### api.js에서 try/catch 제거
+**판단 기준 요약**
-`api.js`에서 `try/catch`로 잡고 `throw`로 다시 던지는 건 `try/catch`를 안 쓴 것과 결과가 같다. 콘솔 로그만 남기고 에러를 그대로 위로 올리기 때문이다. 에러를 처리할 수 있는 곳(훅, 컴포넌트)에서만 잡도록 `api.js`는 단순하게 throw만 하게 변경했다.
+| | 중첩 OK | 분리 권장 |
+|---|---|---|
+| 스타일 복잡도 | 단순 (크기, 색상 1-2개) | 복잡한 스타일 블록 |
+| 재사용 가능성 | 부모 없이 쓰일 일 없음 | 다른 곳에서도 쓰일 수 있음 |
+| 조건부 스타일 | 없음 | props로 분기 필요 |
+| 의미 전달 | 이름 필요 없음 | 이름이 코드 이해에 도움됨 |
-```js
-// Before: try/catch로 잡았다가 다시 throw — 의미 없는 중간 처리
-export async function getRestaurants() {
- try {
- const response = await fetch(`${BASE_URL}/restaurants`);
- if (!response.ok) throw new Error(`서버 오류: ${response.status}`);
- return await response.json();
- } catch (error) {
- console.error("음식점 목록 조회 실패:", error);
- throw error;
- }
-}
-
-// After: 단순하게 throw만
-export async function getRestaurants() {
- const response = await fetch(`${BASE_URL}/restaurants`);
- if (!response.ok) throw new Error(`서버 오류: ${response.status}`);
- return response.json();
-}
-```
+---
-### 에러 핸들링 추가
+## 🛠 리팩토링
-에러는 사용자에게 보여줄 수 있는 가장 가까운 곳에서 한 번만 잡는다. 중간 함수(`addRestaurant`)에서 잡으면 에러가 거기서 소멸되어 최종 호출자가 실패 여부를 알 수 없다.
+### 컴포넌트 파일 구조 단순화
-- 초기 로딩 실패: `fetchRestaurants`의 `try/catch`에서 `error` state에 담아 화면에 표시
-- 음식점 추가 실패: `handleRestaurantSubmit`의 `try/catch`에서 alert로 사용자에게 알림
+기존에는 컴포넌트마다 폴더를 만들어 `ComponentName/ComponentName.jsx` + `ComponentName.module.css` 구조였다. styled-components 전환 후 CSS 파일이 사라지면서 폴더 없이 `ComponentName.jsx` 단일 파일로 정리했다.
-```js
-// useRestaurants.js — 초기 로딩 에러 처리
-const fetchRestaurants = useCallback(async () => {
- setIsLoading(true);
- try {
- const data = await getRestaurants();
- setRestaurants(data);
- } catch (error) {
- setError("음식점 목록을 불러오지 못했습니다.");
- } finally {
- setIsLoading(false);
- }
-}, []);
-
-// App.jsx — 추가 실패 에러 처리
-async function handleRestaurantSubmit(restaurant) {
- try {
- await addRestaurant(restaurant);
- setIsAddRestaurantModalOpen(false);
- } catch {
- alert("음식점 추가에 실패했습니다. 다시 시도해주세요.");
- }
-}
```
+before:
+components/
+ Header/
+ Header.jsx
+ Header.module.css
-### 매직 스트링 상수화 — `ALL_CATEGORY`
-
-`"전체"` 문자열이 `App.jsx`, `CategoryFilter.jsx`, `filterRestaurants.js` 세 곳에 흩어져 있었다. 하나라도 수정하면 나머지도 함께 바꿔야 하는 암묵적 결합이다. 상수로 추출해 한 곳에서 관리한다.
-
-```js
-// Before: 세 곳에 흩어진 매직 스트링
-useState("전체");
-
-if (category === "전체") return restaurants;
-
-// After: 상수로 단일화
-export const ALL_CATEGORY = "전체";
-
-useState(ALL_CATEGORY);
-
-if (category === ALL_CATEGORY) return restaurants;
+after:
+components/
+ Header.jsx
```
-### import 확장자 일관성
+### 자식 태그 선택자 → 별도 styled 컴포넌트로 분리
-Vite 프로젝트에서 일부 파일은 `.jsx`/`.js` 확장자를 명시하고 일부는 생략해 혼재된 상태였다. Vite는 ESM 표준에 가깝게 확장자 명시를 권장하므로 모든 로컬 import에 확장자를 추가했다.
+의미 있는 콘텐츠 요소에 자식 태그 선택자를 사용하던 방식을 각각 별도의 styled 컴포넌트로 분리했다.
```js
-// Before
-import Modal from "../Modal/Modal";
-import { CATEGORIES } from "../../constants/categories";
+// before — 자식 태그 선택자 사용
+const Info = styled.div`
+ h3 { font-size: 18px; font-weight: 600; }
+ p { padding-top: 8px; }
+`;
-// After
-import Modal from "../Modal/Modal.jsx";
-import { CATEGORIES } from "../../constants/categories.js";
+// after — 각각 명시적인 컴포넌트로 분리
+const Info = styled.div`...`;
+const RestaurantName = styled.h3`font-size: 18px; font-weight: 600;`;
+const RestaurantDescription = styled.p`padding-top: 8px;`;
```
-### isLoading state 추가
+적용 파일: `CategoryFilter`, `RestaurantList`, `AddRestaurantModal`
-데이터를 불러오는 동안 화면이 빈 상태로 보이는 문제를 해결하기 위해 `isLoading` state를 추가했다. `finally`를 사용해 성공/실패 여부와 관계없이 로딩 상태가 반드시 해제되도록 했다.
+### RestaurantList 시맨틱 구조 개선
-```js
-const [isLoading, setIsLoading] = useState(false);
-
-const fetchRestaurants = useCallback(async () => {
- setIsLoading(true);
- try {
- const data = await getRestaurants();
- setRestaurants(data);
- } catch (error) {
- setError("음식점 목록을 불러오지 못했습니다.");
- } finally {
- setIsLoading(false); // 성공/실패 모두 로딩 해제
- }
-}, []);
+`styled.section`이 일반 `
`을 감싸는 이중 구조를 `styled.ul`로 단일화했다.
-return { restaurants, addRestaurant, isLoading, error };
-```
+```jsx
+// before — section > ul > li 이중 구조
+const List = styled.section`...`;
+return (
+
+