Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4단계 미션] 박예은 미션 제출합니다 #28

Merged
merged 24 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
fe6716b
feat: 초기세팅
ye6194 Oct 7, 2024
e58cc30
feat: 초기세팅
ye6194 Oct 7, 2024
57849c4
feat: 컴포넌트 그리기
ye6194 Oct 7, 2024
16b7b7f
feat: 스타일 적용
ye6194 Oct 7, 2024
efed817
Merge branch 'step1' of https://github.com/ye6194/self-paced-react in…
ye6194 Oct 8, 2024
c3c5fc7
feat: 카테고리 필터에 따라 필터된 음식점 목록을 보여줌
ye6194 Oct 8, 2024
6c6c45b
feat: 필터된 음식점 목록을 보여줄 때 카테고리에 맞는 이미지 출력
ye6194 Oct 8, 2024
972cf6e
chore: 불필요한 주석 제거
ye6194 Oct 8, 2024
8e5e16b
refactor: data를 다른 파일로 분리
ye6194 Oct 10, 2024
8794dae
refactor: 코드리뷰 반영
ye6194 Oct 14, 2024
80dce59
chore: 불필요한 설정 제거
ye6194 Oct 14, 2024
e02a97a
refactor: useEffect 삭제
ye6194 Oct 14, 2024
29016c0
fix: CRA에서 Vite로 제대로 바꿔지지 않았던 문제 해결
ye6194 Oct 20, 2024
f41ced4
refactor: 코드 리뷰 반영
ye6194 Oct 21, 2024
19d8f6e
push
ye6194 Nov 1, 2024
b73b1f5
Merge remote-tracking branch 'upstream/ye6194' into step2
ye6194 Nov 1, 2024
d364767
commit
ye6194 Nov 4, 2024
71bffb8
feat: 클릭한 레스토랑의 정보를 모달에 표시
ye6194 Nov 4, 2024
cc49523
feat: backdrop을 클릭하면 모달이 닫힘
ye6194 Nov 4, 2024
c6721b7
feat: 음식점 추가 모달 열고 닫는 기능
ye6194 Nov 4, 2024
7124439
feat: 모달의 추가 버튼을 누르면 레스토랑 목록에 추가
ye6194 Nov 4, 2024
c7e8c08
refactor: 코드리뷰 내용 반영 및 수정
ye6194 Nov 6, 2024
f25a0fd
refactor: 코드리뷰 내용 반영
ye6194 Nov 9, 2024
9e06b51
Merge branch 'ye6194' into step4
ye6194 Nov 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import restaurantsData from "./data/restaurantsData.js";

function App() {
const [category, setCategory] = useState("전체");
const [restaurants, setRestaurants] = useState(restaurantsData);

const filterRestaurants = (category) => {
if (category === "전체") return restaurantsData;
else return restaurantsData.filter((restaurant) => restaurant.category === category);
if (category === "전체") return restaurants;
else return restaurants.filter((restaurant) => restaurant.category === category);
};

const filteredRestaurants = filterRestaurants(category);

const [modal, setModal] = useState({
isOpen: false,
restaurant: {
Expand All @@ -25,16 +26,26 @@ function App() {
},
});

const [isAddModalOpen, setIsAddModalOpen] = useState(false);
const handleAddRestaurant = (newRestaurant) => {
setRestaurants((prevRestaurants) => [...prevRestaurants, newRestaurant]);
};

return (
<>
<Header />
<Header setIsAddModalOpen={setIsAddModalOpen} />
<main>
<CategoryFilter category={category} onChangeCategory={setCategory} />
<RestaurantList restaurants={filteredRestaurants} setModal={setModal} modal={modal} />
</main>
<aside>
{modal.isOpen && <RestaurantDetailModal setModal={setModal} modal={modal} />}
{/* <AddRestaurantModal /> */}
{isAddModalOpen && (
<AddRestaurantModal
setIsAddModalOpen={setIsAddModalOpen}
handleAddRestaurant={handleAddRestaurant}
/>
)}
</aside>
</>
);
Expand Down
51 changes: 44 additions & 7 deletions src/components/AddRestaurantModal.jsx
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인터뷰 🎤

제어 컴포넌트(controlled component)와 비제어 컴포넌트(uncontrolled component)란 무엇인가요? 각 방법에서 부각되는 특징은 무엇이라고 생각하시나요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 제어 컴포넌트(controlled component)
    입력값이 상태에 의해 제어되는 컴포넌트입니다. input, textarea 등 입력 필드의 값이 바뀔 때마다 업데이트됩니다.

  • 비제어 컴포넌트(uncontrolled component)
    입력값이 상태에 의해 제어되지 않고 입력 필드가 값을 관리하는 컴포넌트 입니다. useRef를 사용해 필요할 때 입력값을 가져옵니다.

각 방법에서 부각되는 특징은 상태가 연결됐는지(컴포넌트와 입력값의 동기화) 여부라고 생각합니다!

Original file line number Diff line number Diff line change
@@ -1,36 +1,73 @@
import "../styles/AddRestaurantModalStyle.css";
import { CATEGORY_DATA } from "../data/categoryData";
import { useState } from "react";

function AddRestaurantModal({ setIsAddModalOpen, handleAddRestaurant }) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleAddRestaurant

깔끔하고 직관적인 이름이네요 👁️

const [newRestaurant, setNewRestaurant] = useState({
id: Date.now(),
name: "",
description: "",
category: "",
});

const handleAddBtnClick = () => {
handleAddRestaurant(newRestaurant);
setIsAddModalOpen(false);
};

function AddRestaurantModal() {
return (
<div className="modal modal--open">
<div className="modal-backdrop"></div>
<div className="modal-backdrop" onClick={() => setIsAddModalOpen(false)}></div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍

<div className="modal-container">
<h2 className="modal-title text-title">새로운 음식점</h2>
<form>
<div className="form-item form-item--required">
<label htmlFor="category text-caption">카테고리</label>
<select name="category" id="category" required>
<select
name="category"
id="category"
required
onChange={(event) =>
setNewRestaurant({ ...newRestaurant, category: event.target.value })
}
>
<option value="">선택해 주세요</option>
{CATEGORY_DATA.slice(1).map((category) => (
<option key={category}>{category}</option>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인터뷰 🎤

categorykey 값으로 정해주신 이유에 대해 생각을 설명해주실 수 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저번주 스터디에서 배운 내용을 적용했어요!!

key prop이 바뀌면 리렌더링 됩니다. 따라서 아래 두 가지 상황을 피해야 한다고 배웠어요.

  • key에 즉석에서 생성한 값을 전달
  • key에 배열의 인덱스를 전달
    (배열 [ 'a', 'b', 'c', 'd']가 [ 'a', 'e', 'b', 'c', 'd']가 되면 'e'의 인덱스가 2가 되고 'b', 'c', 'd'의 인덱스는 1씩 밀리게 됩니다.
    이렇게 되면 'a'의 key를 제외하고 모든 key가 바뀌어 불필요하게 렌더링됩니다.)

이러한 이유로 categorykey값으로 적용했답니다.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

key를 사용할 때 피해야 하는 상황을 잘 말씀해주셨네요 🚀

말씀해주신 예시인 [즉석에서 생성한 값을 전달] 의 경우에는 매 리렌더링마다 보통 새로운 값이 랜덤으로 생성되는 방법을 사용하기 때문에 React에서 key 값을 이용해 각 항목들을 식별하는 의미가 없어집니다. 항목이 계속해서 같은 키 값을 가지고 있어야 리스트의 여러 항목이 변경되더라도 그 항목을 식별하고 이를 이용해 더 최적화된 연산을 수행할 수 있는데, 매번 키 값이 새롭게 생성되면 React 입장에서는 실제로는 항목이 이동한 것에 불과한데도 새로운 항목이 추가된 것으로 인식하고 불필요한 연산들을 수행하려 할 겁니다.

index 를 키 값으로 사용하는 것도 비슷한 맥락입니다.

여기에 말씀하신 내용에 대해 좀 더 자세한 내용을 적어드릴게요.

리렌더링은 React.memo 등의 특수한 메모이제이션을 사용하지 않는 한 막을 수 없습니다. 리스트에서 부모 컴포넌트가 리렌더링되면 자식 컴포넌트는 리렌더링됩니다. 이건 기본 동작이자 원칙이며, key 값과 별개로 적용됩니다. 리렌더링이 되지 않으면 리스트를 지니고 있는 부모 컴포넌트의 상태가 바뀌어도 변경된 UI가 적용이 되지 않겠죠?

적절하게 key 값을 사용했을 때 작성하신 코드는 재조정(Reconcilation) 과정에서 활약할 수 있게 됩니다. 컴포넌트가 리렌더링되야 할 경우 React는 변경 사항을 비교하고 이를 기반으로 새로운 DOM 트리를 그려야 합니다. 이 때 key값을 기반으로 매번 트리를 새로 그리거나 새로운 DOM 요소를 비싼 비용으로 추가하지 않고 기존에 존재하는 DOM을 재사용할 수 있게 됩니다.

))}
</select>

</div>

<div className="form-item form-item--required">
<label htmlFor="name text-caption">이름</label>
<input type="text" name="name" id="name" required />
<input
type="text"
name="name"
id="name"
required
onChange={(event) => setNewRestaurant({ ...newRestaurant, name: event.target.value })}
/>
</div>

<div className="form-item">
<label htmlFor="description text-caption">설명</label>
<textarea name="description" id="description" cols="30" rows="5"></textarea>
<textarea
name="description"
id="description"
cols="30"
rows="5"
value={newRestaurant.description}
onChange={(event) =>
setNewRestaurant({ ...newRestaurant, description: event.target.value })
}
></textarea>
<span className="help-text text-caption">메뉴 등 추가 정보를 입력해 주세요.</span>
</div>

<div className="button-container">
<button className="button button--primary text-caption">추가하기</button>
<button className="button button--primary text-caption" onClick={handleAddBtnClick}>
추가하기
</button>
</div>
</form>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/CategoryFilter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function CategoryFilter({ category, onChangeCategory }) {
className="restaurant-filter"
aria-label="음식점 카테고리 필터"
value={category}
onChange={() => onChangeCategory(event.target.value)}
onChange={(event) => onChangeCategory(event.target.value)}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔨 ✅ 👍🏻

>
{CATEGORY_DATA.map((category) => (
<option key={category}>{category}</option>
Expand Down
9 changes: 7 additions & 2 deletions src/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import "../styles/HeaderStyle.css";

function Header() {
function Header({ setIsAddModalOpen }) {
return (
<>
<header className="gnb">
<h1 className="gnb__title text-title">점심 뭐 먹지</h1>
<button type="button" className="gnb__button" aria-label="음식점 추가">
<button
type="button"
className="gnb__button"
aria-label="음식점 추가"
onClick={() => setIsAddModalOpen(true)}
>
<img src="../../templates/add-button.png" alt="음식점 추가" />
</button>
</header>
Expand Down
2 changes: 1 addition & 1 deletion src/components/RestaurantList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function RestaurantList({ restaurants, setModal, modal }) {
<div className="restaurant__category">
<img
src={`../../templates/category-${CATEGORY_IN_ENGLISH[restaurant.category]}.png`}
alt="한식"
alt=""
className="category-icon"
/>
</div>
Expand Down