-
Notifications
You must be signed in to change notification settings - Fork 28
[4단계 미션] 박예은 미션 제출합니다 #28
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
Changes from 21 commits
fe6716b
e58cc30
57849c4
16b7b7f
efed817
c3c5fc7
6c6c45b
972cf6e
8e5e16b
8794dae
80dce59
e02a97a
29016c0
f41ced4
19d8f6e
b73b1f5
d364767
71bffb8
cc49523
c6721b7
7124439
c7e8c08
f25a0fd
9e06b51
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,48 +1,82 @@ | ||||||||||||||||||||||||||||||||
| import "../styles/AddRestaurantModalStyle.css"; | ||||||||||||||||||||||||||||||||
| import { CATEGORY_DATA } from "../data/categoryData"; | ||||||||||||||||||||||||||||||||
| import restaurantsData from "../data/restaurantsData"; | ||||||||||||||||||||||||||||||||
| import { useState } from "react"; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| function AddRestaurantModal({ setIsAddModalOpen }) { | ||||||||||||||||||||||||||||||||
| const [selectedCategory, setSelectedCategory] = useState(""); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const handleAddBtnClick = () => { | ||||||||||||||||||||||||||||||||
| event.preventDefault(); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| restaurantsData.push({ | ||||||||||||||||||||||||||||||||
| id: Date.now(), | ||||||||||||||||||||||||||||||||
| name: restaurantName, | ||||||||||||||||||||||||||||||||
| description: restaurantInfo, | ||||||||||||||||||||||||||||||||
| category: selectedCategory, | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게
따라서 이를 이유로 피해주셔야 할 방법이라고 생각합니다. 이렇게 레스토랑의 정보를 변경해야 하는 상황인데,
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 물론 제가 이렇게 말씀드려도 당장은 작동하는 것처럼 보이기 때문에 "왜 이런 지적을...? ❓" 이렇게 느끼실 수 있다고 생각해요. 그래서 한 번 문제가 생길 수 있는 상황을 재현해보았어요. 결론부터 말씀드리자면 이렇게 해도 동작할 수 있었던 것은 우선, const handleAddBtnClick = () => {
event.preventDefault();
restaurantsData.push({
id: Date.now(),
name: restaurantName,
description: restaurantInfo,
category: selectedCategory,
});
alert("handleAddBtnClick() 실행!"); // 추가
// setIsAddModalOpen(false); <-- 테스트를 위해 제거
};
그 다음 평소대로 레스토랑 추가 모달을 열어 추가하면, 의도했던 것과 다르게 레스토랑이 추가되지 않는 모습을 확인하실 수 있을 거에요. (모달이 닫히는 로직을 제거했으므로 직접 HTML의 요소를 개발자 도구를 통해 제거하는 방법을 사용했습니다) _2024_11_08_11_45_21_921.mp4
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. import한 객체를 직접 업데이트하면 이런 문제가 생길 수 있군요..!! |
||||||||||||||||||||||||||||||||
| setIsAddModalOpen(false); | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 데이터를 모달 창 내에서 추가한다는 생각은 못했는데 이런 방법도 존재했었네요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gogo1414 이 질문에 답변드리자면, 하위 컴포넌트에서 상위 컴포넌트의 함수를 실행해 데이터를 업데이트하는 방법은 역방향 흐름인 것 처럼 작동하지만, 단방향 흐름의 일환으로 해석할 수 있으며, React의 단방향 흐름 원칙을 위반하지는 않는 사례입니다. 오히려 React에서는 이 방법을 권장하고 있으며, 여러 컴포넌트가 데이터를 공유해야 할 경우 부모 컴포넌트에 state를 두어 관리하는 방법 또한 설명하고 있습니다. 역방향처럼 보이지만 단방향 흐름이라는 것을 이해하기가 어렵거나 헷갈리신다면, 데이터가 흐르는 방향 에 주목하시면 좋을 것 같아요. 자식 컴포넌트가 부모 컴포넌트에게 값의 업데이트를 요청하기 위해 부모 컴포넌트의 함수를 실행하더라도, 데이터를 업데이트 하는 로직 자체는 부모 컴포넌트가 수행하며, 데이터는 여전히 부모 컴포넌트에서 자식 컴포넌트로 내려가는 형식입니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 쉽게 설명해주셔서 바로 이해했습니다! 감사합니다😊 |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const [restaurantName, setRestaurantName] = useState(""); | ||||||||||||||||||||||||||||||||
| const [restaurantInfo, setRestaurantInfo] = useState(""); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| function AddRestaurantModal() { | ||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||
| <div className="modal modal--open"> | ||||||||||||||||||||||||||||||||
| <div className="modal-backdrop"></div> | ||||||||||||||||||||||||||||||||
| <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> | ||||||||||||||||||||||||||||||||
| <option value="">선택해 주세요</option> | ||||||||||||||||||||||||||||||||
| <option value="한식">한식</option> | ||||||||||||||||||||||||||||||||
| <option value="중식">중식</option> | ||||||||||||||||||||||||||||||||
| <option value="일식">일식</option> | ||||||||||||||||||||||||||||||||
| <option value="양식">양식</option> | ||||||||||||||||||||||||||||||||
| <option value="아시안">아시안</option> | ||||||||||||||||||||||||||||||||
| <option value="기타">기타</option> | ||||||||||||||||||||||||||||||||
| </select> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| {/* 음식점 이름 */} | ||||||||||||||||||||||||||||||||
| <div className="form-item form-item--required"> | ||||||||||||||||||||||||||||||||
| <label htmlFor="name text-caption">이름</label> | ||||||||||||||||||||||||||||||||
| <input type="text" name="name" id="name" required /> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| {/* 설명 */} | ||||||||||||||||||||||||||||||||
| <div className="form-item"> | ||||||||||||||||||||||||||||||||
| <label htmlFor="description text-caption">설명</label> | ||||||||||||||||||||||||||||||||
| <textarea name="description" id="description" cols="30" rows="5"></textarea> | ||||||||||||||||||||||||||||||||
| <span className="help-text text-caption">메뉴 등 추가 정보를 입력해 주세요.</span> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| {/* 추가 버튼 */} | ||||||||||||||||||||||||||||||||
| <div className="button-container"> | ||||||||||||||||||||||||||||||||
| <button className="button button--primary text-caption">추가하기</button> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </form> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| <div className="modal modal--open"> | ||||||||||||||||||||||||||||||||
| <div className="modal-backdrop" onClick={() => setIsAddModalOpen(false)}></div> | ||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||||||||||||||||||||||||||||
| onChange={(selected) => setSelectedCategory(selected.target.value)} | ||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||
| <option value="">선택해 주세요</option> | ||||||||||||||||||||||||||||||||
| {CATEGORY_DATA.map((category) => ( | ||||||||||||||||||||||||||||||||
| <option key={category}>{category}</option> | ||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 인터뷰 🎤
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저번주 스터디에서 배운 내용을 적용했어요!! key prop이 바뀌면 리렌더링 됩니다. 따라서 아래 두 가지 상황을 피해야 한다고 배웠어요.
이러한 이유로 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. key를 사용할 때 피해야 하는 상황을 잘 말씀해주셨네요 🚀 말씀해주신 예시인 [즉석에서 생성한 값을 전달] 의 경우에는 매 리렌더링마다 보통 새로운 값이 랜덤으로 생성되는 방법을 사용하기 때문에 React에서 key 값을 이용해 각 항목들을 식별하는 의미가 없어집니다. 항목이 계속해서 같은 키 값을 가지고 있어야 리스트의 여러 항목이 변경되더라도 그 항목을 식별하고 이를 이용해 더 최적화된 연산을 수행할 수 있는데, 매번 키 값이 새롭게 생성되면 React 입장에서는 실제로는 항목이 이동한 것에 불과한데도 새로운 항목이 추가된 것으로 인식하고 불필요한 연산들을 수행하려 할 겁니다.
여기에 말씀하신 내용에 대해 좀 더 자세한 내용을 적어드릴게요. 리렌더링은 적절하게 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 | ||||||||||||||||||||||||||||||||
| onChange={(input) => setRestaurantName(input.target.value)} | ||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 우선 구현해 주신 핸들러 로직은 적합하고 정석적인 좋은 값 업데이트 방법 중 하나에요 👍🏻 잘 구현하셨어요 onChange={(event) => setRestaurantName(event.target.value)}
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵 수정하겠습니다 |
||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| {/* 설명 */} | ||||||||||||||||||||||||||||||||
| <div className="form-item"> | ||||||||||||||||||||||||||||||||
| <label htmlFor="description text-caption">설명</label> | ||||||||||||||||||||||||||||||||
| <textarea | ||||||||||||||||||||||||||||||||
| name="description" | ||||||||||||||||||||||||||||||||
| id="description" | ||||||||||||||||||||||||||||||||
| cols="30" | ||||||||||||||||||||||||||||||||
| rows="5" | ||||||||||||||||||||||||||||||||
| onChange={(input) => setRestaurantInfo(input.target.value)} | ||||||||||||||||||||||||||||||||
| ></textarea> | ||||||||||||||||||||||||||||||||
|
Comment on lines
+58
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
React에 의해 제어되도록 제어 컴포넌트를 사용하시기로 결정하셨다면, 이 요소의
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호... 리액트에서 상태는 생각보다 엄청 중요한 개념이네요.
명심하겠습니다!! |
||||||||||||||||||||||||||||||||
| <span className="help-text text-caption">메뉴 등 추가 정보를 입력해 주세요.</span> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| {/* 추가 버튼 */} | ||||||||||||||||||||||||||||||||
| <div className="button-container"> | ||||||||||||||||||||||||||||||||
| <button className="button button--primary text-caption" onClick={handleAddBtnClick}> | ||||||||||||||||||||||||||||||||
| 추가하기 | ||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </form> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
인터뷰 🎤
제어 컴포넌트(controlled component)와 비제어 컴포넌트(uncontrolled component)란 무엇인가요? 각 방법에서 부각되는 특징은 무엇이라고 생각하시나요?
There was a problem hiding this comment.
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를 사용해 필요할 때 입력값을 가져옵니다.
각 방법에서 부각되는 특징은 상태가 연결됐는지(컴포넌트와 입력값의 동기화) 여부라고 생각합니다!