-
Notifications
You must be signed in to change notification settings - Fork 9
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
Conversation
- import 해서 map() 사용
- 불필요한 코드 삭제 - 파일 분리 (categoryInEnglish) - export, import 방식 변경
- uninstall react-scripts
- 화면은 그려지지만 개발자 도구의 콘솔에 아무것도 뜨지 않고 필터가 작동하지 않는 문제가 있었음 - index.js 파일 삭제 - /public/index.html을 루트로 옮김 - 원래 루트에 있던 index.html을 templates 폴더로 옮김 - public에 있던 .css, .png 파일들을 templates 폴더로 옮김 - App.jsx의 import문에서 확장자 명시
- categoryData, categoryInEnglish를 UPPER_SNAKE_CASE로 변경 - CATEGORY_IN_ENGLISH을 RestaurantList.jsx로 이동
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.
안녕하세요, 예은님😊
리뷰어 김준수입니다!
4단계 미션도 고생하셨어요!!
그리고 onChange={(selected) => setSelectedCategory(selected.target.value)} 이 부분을 작성하면서 작동 원리가 헷갈렸는데요.
찾아보니 selected가 onChange 이벤트가 발생할 때 자동으로 전달되는 이벤트 객체,
selected.target은 이벤트가 발생한 DOM 요소(select 태그),
selected.target.value는 사용자가 select 태그에서 선택한 option의 value 속성 값을 나타낸다는 것을 알게되었어요.
-> 예은님이 작성하신 글을 보고 이것저것 찾아보 이벤트 핸들러 함수와 useState를 보통 함께 사용하고 있는 걸 깨달았네요..!!
시간이 없어서 선택 미션인 재사용 가능한 Modal 컴포넌트 구현은 못했습니다😢
-> 시간이 있으실 때 해보시면 좋을거 같네요😊
4단계 미션도 고생하셨습니다!
const [selectedCategory, setSelectedCategory] = useState(""); | ||
|
||
const handleAddBtnClick = () => { | ||
event.preventDefault(); | ||
|
||
restaurantsData.push({ | ||
id: Date.now(), | ||
name: restaurantName, | ||
description: restaurantInfo, | ||
category: selectedCategory, | ||
}); | ||
setIsAddModalOpen(false); | ||
}; |
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.
데이터를 모달 창 내에서 추가한다는 생각은 못했는데 이런 방법도 존재했었네요!
저의 경우 레스토랑 데이터를 맨 처음 불러온 곳이 App.jsx 가장 상위 컴포넌트라 데이터의 저장도 이곳에서 이루어지는게 좋다고 생각했는데,저장을 위해 데이터가 역방향으로 흐르는게 좋은 방식인지 고민을 안해봤던거 같네요... (고민거리 스택+1)
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.
@gogo1414 이 질문에 답변드리자면, 하위 컴포넌트에서 상위 컴포넌트의 함수를 실행해 데이터를 업데이트하는 방법은 역방향 흐름인 것 처럼 작동하지만, 단방향 흐름의 일환으로 해석할 수 있으며, React의 단방향 흐름 원칙을 위반하지는 않는 사례입니다. 오히려 React에서는 이 방법을 권장하고 있으며, 여러 컴포넌트가 데이터를 공유해야 할 경우 부모 컴포넌트에 state를 두어 관리하는 방법 또한 설명하고 있습니다.
역방향처럼 보이지만 단방향 흐름이라는 것을 이해하기가 어렵거나 헷갈리신다면, 데이터가 흐르는 방향 에 주목하시면 좋을 것 같아요. 자식 컴포넌트가 부모 컴포넌트에게 값의 업데이트를 요청하기 위해 부모 컴포넌트의 함수를 실행하더라도, 데이터를 업데이트 하는 로직 자체는 부모 컴포넌트가 수행하며, 데이터는 여전히 부모 컴포넌트에서 자식 컴포넌트로 내려가는 형식입니다.
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.
쉽게 설명해주셔서 바로 이해했습니다!
감사합니다😊
return ( | ||
<div className="modal modal--open"> | ||
<div className="modal-backdrop"></div> | ||
<div className="modal-backdrop" onClick={() => setIsAddModalOpen(false)}></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.
👍👍
<select name="category" id="category" required> | ||
<select | ||
name="category" | ||
id="category" | ||
required | ||
onChange={(selected) => setSelectedCategory(selected.target.value)} | ||
> |
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.
한눈에 보여서 좋네요!
<option value="">선택해 주세요</option> | ||
{CATEGORY_DATA.map((category) => ( |
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.
요 부분 3단계에서도 말씀 드렸었는데 리마인드 차원에서 한번 더 말씀드릴게요!
전체라는 카테고리는 선택 항목에서 없는게 좋다고 느껴져서 해당 부분 고민해보시면 좋을거 같아요!!
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.
넵 저도 말했지만 인지는 했는데 그냥 안고치고 있었어서ㅎ... 고치겠습니다!
- 불필요한 주석 삭제 - RestaurantDetailModal에서 handleClick 함수 삭제 - CategoryFilter에서 handleChange 함수 삭제 - 새로운 음식점 추가 시 카테고리에서 '전체' 항목 삭제 - SetIsModalOpen -> set~ 오타 수정 - Header에서 handleClick 함수 삭제
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.
고생하셨습니다!!
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.
@ye6194 👋🏻
중간고사 보시느라 수고 많으셨습니다! 저는 휴학한 지 2년이 다 되가다 보니 점점 대학교에서의 기억이 가물가물해지는 것 같습니다 ㅋㅋ... 가끔 제가 대학생이라는 걸 잊어버리기도 합니다.
4단계에서는 전반적으로 폼을 다루고, 이 폼에서의 데이터 및 상황을 다룰 수 있는 방법으로 제어 컴포넌트(controlled component)와 비제어 컴포넌트(uncontrolled component)가 언급되었어요. 자세히 파실 필요는 없고 이게 어떠한 개념인가~ 정도로만 일단 이해해주신다면 그것으로 충분하다고 생각합니다.
두 방법의 장단점이라면 필요 시 스터디원들과도 스터디 시간에 주제를 다뤄보는 것은 어떨까 하는 생각도 듭니다. 물론 저한테 질문해주셔도 좋아요. 저는 인상 깊게 읽었던 두 레퍼런스를 공유해 드릴게요.
재사용 가능한 모달은 어쩔 수 없지만, 이번 단계에서는 괜찮으니 5단계 미션에서 가능하면 이러한 재사용 시도를 (필요성이 느껴지신다면) 해 보시기 바래요. 사실 모달 말고도 반복되는 요소들, 범용적으로 사용될 수 있는 요소들...! 많이 보일 것입니다. 개선할 수 있는 부분은 많아요.
1️⃣ 리뷰
우선 자세히 본인이 구현해 주신 것을 리뷰어에게 설명해 주신 것은 좋은 습관이자 노력이라고 생각합니다. 이번에는 저와 메인테이너님이 절반씩 리뷰를 맡기로 결정했기 때문에 각 단계에서 무엇을 구현하신 건지, 요구사항이 무엇인지를 파악하는데 전반적으로 어려움이 있었는데, PR을 자세히 작성해 주셔서 그나마 파악하기 수월했어요.
기능적으로 보았을 때는 전반적으로 4단계의 요구사항에 맞게 잘 구현해주셨지만, 3단계에서 구현해주신 클릭한 음식점을 표시해주는 모달 이 닫히지 않는 문제가 있고, 4단계 코드에서는 구현해 주신 코드 중에서 상태 관리와 관련해 취약할 수 있는 부분이 있었어요. 이 부분은 코멘트로 달아드렸으므로, 꼭 문제를 해결해주셨으면 좋겠는 바램입니다. 🔧
일관성 면에서는 대부분 어색함이 느껴지지 않을 정도로 잘 지켜주시면서 구현해 주신 것 같아요. 앞으로도 이 페이스를 쭉 유지해주시길 바래요 💪🏻
그럼, 코멘트 확인 부탁드립니다! 예은님의 의견 기다리고 있겠습니다 🙆🏻♀️
> | ||
<option value="">선택해 주세요</option> | ||
{CATEGORY_DATA.slice(1).map((category) => ( | ||
<option key={category}>{category}</option> |
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.
인터뷰 🎤
category
를 key
값으로 정해주신 이유에 대해 생각을 설명해주실 수 있을까요?
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.
저번주 스터디에서 배운 내용을 적용했어요!!
key prop이 바뀌면 리렌더링 됩니다. 따라서 아래 두 가지 상황을 피해야 한다고 배웠어요.
- key에 즉석에서 생성한 값을 전달
- key에 배열의 인덱스를 전달
(배열 [ 'a', 'b', 'c', 'd']가 [ 'a', 'e', 'b', 'c', 'd']가 되면 'e'의 인덱스가 2가 되고 'b', 'c', 'd'의 인덱스는 1씩 밀리게 됩니다.
이렇게 되면 'a'의 key를 제외하고 모든 key가 바뀌어 불필요하게 렌더링됩니다.)
이러한 이유로 category
를 key
값으로 적용했답니다.
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.
key를 사용할 때 피해야 하는 상황을 잘 말씀해주셨네요 🚀
말씀해주신 예시인 [즉석에서 생성한 값을 전달] 의 경우에는 매 리렌더링마다 보통 새로운 값이 랜덤으로 생성되는 방법을 사용하기 때문에 React에서 key 값을 이용해 각 항목들을 식별하는 의미가 없어집니다. 항목이 계속해서 같은 키 값을 가지고 있어야 리스트의 여러 항목이 변경되더라도 그 항목을 식별하고 이를 이용해 더 최적화된 연산을 수행할 수 있는데, 매번 키 값이 새롭게 생성되면 React 입장에서는 실제로는 항목이 이동한 것에 불과한데도 새로운 항목이 추가된 것으로 인식하고 불필요한 연산들을 수행하려 할 겁니다.
index
를 키 값으로 사용하는 것도 비슷한 맥락입니다.
여기에 말씀하신 내용에 대해 좀 더 자세한 내용을 적어드릴게요.
리렌더링은 React.memo
등의 특수한 메모이제이션을 사용하지 않는 한 막을 수 없습니다. 리스트에서 부모 컴포넌트가 리렌더링되면 자식 컴포넌트는 리렌더링됩니다. 이건 기본 동작이자 원칙이며, key
값과 별개로 적용됩니다. 리렌더링이 되지 않으면 리스트를 지니고 있는 부모 컴포넌트의 상태가 바뀌어도 변경된 UI가 적용이 되지 않겠죠?
적절하게 key 값을 사용했을 때 작성하신 코드는 재조정(Reconcilation) 과정에서 활약할 수 있게 됩니다. 컴포넌트가 리렌더링되야 할 경우 React는 변경 사항을 비교하고 이를 기반으로 새로운 DOM 트리를 그려야 합니다. 이 때 key값을 기반으로 매번 트리를 새로 그리거나 새로운 DOM 요소를 비싼 비용으로 추가하지 않고 기존에 존재하는 DOM을 재사용할 수 있게 됩니다.
restaurantsData.push({ | ||
id: Date.now(), | ||
name: restaurantName, | ||
description: restaurantInfo, | ||
category: selectedCategory, | ||
}); |
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.
이렇게 import
한 객체를 직접 업데이트할 경우, 아래의 문제들이 발생할 수 있어요.
- React가 이 값이 업데이트되었음을 인지하지 못해 리렌더링 등의 적절한 조치를 취하지 못할 수 있어요. 그래서
restaurantData
의 값이 바뀌더라도 이를 사용하는 컴포넌트들이 새로운 데이터로 업데이트되지 않을 수도 있습니다. 이로 인해 UI가 최신의 상태를 보여준다는 보장을 하기 어렵고, 신뢰성이 떨어지는 컴포넌트가 될 수도 있어요. - React 개발자 도구를 사용하는 경우에도 변경 내용 및 버그 발생 시 이를 추적하지 못할 수도 있어요.
restaurantsData
는 import만 해 준다면 어디에서든 사용할 수 있기 때문에, 아무 컴포넌트나 조작하여 변경할 수 있는 데이터가 되며, 이로 인해 은닉성을 잃게 될 수도 있습니다. 이로 인해 예측이 어려워지고 다른 곳에서의 예상치 못한 값 변경으로 버그가 생길 수도 있어보여요.
따라서 이를 이유로 피해주셔야 할 방법이라고 생각합니다.
이렇게 레스토랑의 정보를 변경해야 하는 상황인데, AddRestaurantModal
컴포넌트가 레스토랑 관련 데이터를 지니지 않아 로직 구현이 어려운 상황이라면, AddRestaurantModal
은 부모에게 레스토랑 데이터가 업데이트 되야함을 알리기만 하고 이 컴포넌트를 부모로 둔 부모 컴포넌트가 이 업데이트 로직을 대신 수행하도록 구현해 보실 것을 추천드릅니다.
<App>
에서도 이 레스토랑 데이터는 가능한 한 상태로 관리하도록 개선을 부탁드리고 싶습니다. 상태에 직접 값을 할당하는 것이 아니라 굳이 setter 함수를 사용하는 이유와 비슷한 케이스이니 고민해 보시면 좋을 것 같아요 😃
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.
물론 제가 이렇게 말씀드려도 당장은 작동하는 것처럼 보이기 때문에 "왜 이런 지적을...? ❓" 이렇게 느끼실 수 있다고 생각해요. 그래서 한 번 문제가 생길 수 있는 상황을 재현해보았어요.
결론부터 말씀드리자면 이렇게 해도 동작할 수 있었던 것은 setIsAddModalOpen()
setter 함수가 실행되어 리렌더링을 트리거했기 때문이에요. 그래서 setIsAddModalOpen()
를 제거해 보려고 해요.
우선, handleAddBtnClick()
을 아래와 같이 변경해볼게요.
const handleAddBtnClick = () => {
event.preventDefault();
restaurantsData.push({
id: Date.now(),
name: restaurantName,
description: restaurantInfo,
category: selectedCategory,
});
alert("handleAddBtnClick() 실행!"); // 추가
// setIsAddModalOpen(false); <-- 테스트를 위해 제거
};
setIsAddModalOpen(false)
를 실행시키지 않도록 해 준 뒤, 함수가 실행되었음을 확인하기 위해 alert
를 추가한 것입니다.
그 다음 평소대로 레스토랑 추가 모달을 열어 추가하면, 의도했던 것과 다르게 레스토랑이 추가되지 않는 모습을 확인하실 수 있을 거에요. (모달이 닫히는 로직을 제거했으므로 직접 HTML의 요소를 개발자 도구를 통해 제거하는 방법을 사용했습니다)
_2024_11_08_11_45_21_921.mp4
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.
import한 객체를 직접 업데이트하면 이런 문제가 생길 수 있군요..!!
덕분에 알아갑니다. App에서 레스토랑 데이터를 상태로 관리하고 업데이트 하도록 수정할게요!
name="name" | ||
id="name" | ||
required | ||
onChange={(input) => setRestaurantName(input.target.value)} |
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.
우선 구현해 주신 핸들러 로직은 적합하고 정석적인 좋은 값 업데이트 방법 중 하나에요 👍🏻 잘 구현하셨어요
여기에 작은 제안을 드리자면, 이 로직에서의 input
파라미터의 경우 실제로는 인풋 엘리먼트(요소) 그 자체라기보다는 이벤트 객체를 담고 있는 별개의 파라미터에요. 따라서 개발자들이 input
을 이벤트 객체라고 더 쉽게 인지할 수 있도록 e
, event
등의 네이밍을 더 추천드리고 싶어요
onChange={(event) => setRestaurantName(event.target.value)}
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.
넵 수정하겠습니다
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를 사용해 필요할 때 입력값을 가져옵니다.
각 방법에서 부각되는 특징은 상태가 연결됐는지(컴포넌트와 입력값의 동기화) 여부라고 생각합니다!
const [selectedCategory, setSelectedCategory] = useState(""); | ||
|
||
const handleAddBtnClick = () => { | ||
event.preventDefault(); | ||
|
||
restaurantsData.push({ | ||
id: Date.now(), | ||
name: restaurantName, | ||
description: restaurantInfo, | ||
category: selectedCategory, | ||
}); | ||
setIsAddModalOpen(false); | ||
}; |
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.
@gogo1414 이 질문에 답변드리자면, 하위 컴포넌트에서 상위 컴포넌트의 함수를 실행해 데이터를 업데이트하는 방법은 역방향 흐름인 것 처럼 작동하지만, 단방향 흐름의 일환으로 해석할 수 있으며, React의 단방향 흐름 원칙을 위반하지는 않는 사례입니다. 오히려 React에서는 이 방법을 권장하고 있으며, 여러 컴포넌트가 데이터를 공유해야 할 경우 부모 컴포넌트에 state를 두어 관리하는 방법 또한 설명하고 있습니다.
역방향처럼 보이지만 단방향 흐름이라는 것을 이해하기가 어렵거나 헷갈리신다면, 데이터가 흐르는 방향 에 주목하시면 좋을 것 같아요. 자식 컴포넌트가 부모 컴포넌트에게 값의 업데이트를 요청하기 위해 부모 컴포넌트의 함수를 실행하더라도, 데이터를 업데이트 하는 로직 자체는 부모 컴포넌트가 수행하며, 데이터는 여전히 부모 컴포넌트에서 자식 컴포넌트로 내려가는 형식입니다.
<textarea | ||
name="description" | ||
id="description" | ||
cols="30" | ||
rows="5" | ||
onChange={(input) => setRestaurantInfo(input.target.value)} | ||
></textarea> |
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.
<textarea | |
name="description" | |
id="description" | |
cols="30" | |
rows="5" | |
onChange={(input) => setRestaurantInfo(input.target.value)} | |
></textarea> | |
<textarea | |
name="description" | |
id="description" | |
cols="30" | |
rows="5" | |
value={restaurantInfo} | |
onChange={(input) => setRestaurantInfo(input.target.value)} | |
></textarea> |
React에 의해 제어되도록 제어 컴포넌트를 사용하시기로 결정하셨다면, 이 요소의 value
또한 React에서 내려주는 state를 사용하실 것을 강력히 권장해 드리고 싶어요. 아래에 이유를 적어볼게요 ✏️
onChange
이벤트가 발생하는 경우restaurantInfo
state가 업데이트되므로 당장은 문제가 없는 것처럼 보일 수 있어요.- 그러나,
restaurantInfo
state가 다른 로직에 의해 업데이트되는 일이 생길 때에는 이<textarea>
컴포넌트는value
값이 React의 state로 되어 있지 않기 때문에 React의 최신화된 state 값이 반영되지 않아요. "다른 로직에 의해 업데이트 되는 일" 을 아래에 조금 설명해 볼게요.- 이후 [지우기] 버튼을 추가해
restaurantInfo
를 포함한 모든 정보를 지우는 로직을 추가한다면? - 몇몇 폼들은 하나의 인풋이 변경되었을 때 여러 개의 인풋의 정보가 자동으로 바뀌도록 하거나, 보이는 메뉴를 다르게 하기도 하는 기능을 구현한다면?
- 이후 [지우기] 버튼을 추가해
- 즉, 이러한 작업을 해 주는 이유는 이후 state가 변화하더라도 정확한 값을 지닐 수 있도록 동기화 해 주기 위함이에요.
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.
오호... 리액트에서 상태는 생각보다 엄청 중요한 개념이네요.
React에 의해 제어되도록 제어 컴포넌트를 사용하시기로 결정하셨다면, 이 요소의 value 또한 React에서 내려주는 state를 사용하실 것을 강력히 권장해 드리고 싶어요.
명심하겠습니다!!
안녕하세요 의천님🙌
이건 분명히 기능이 작동하는걸 확인했는데 왜 안되나 봤더니
import한 객체를 직접 업데이트 했을 때 생기는 문제들이 인상깊었어요. 항상 데이터의 최신화, 상태를 염두에 두고 코드를 짜야겠어요. 코드리뷰 하시느라 수고하셨습니다! |
- step3 코드리뷰 내용 적용 - App에서 레스토랑 데이터를 상태로 관리 - 이벤트 객체 이름 event로 통일
const [isModalOpen, setIsModalOpen] = useState(false);
const [restaurantName, setRestaurantName] = useState("");
const [restaurantInfo, setRestaurantInfo] = useState(""); 이 코드가 const [modal, setModal] = useState({
isOpen: false,
restaurant: {
name: "",
description: "",
},
}); 이렇게 바뀐건 step3 코드리뷰 내용을 반영한 것입니다! |
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.
수고하셨어요 ✅ 여러 방면에서 빡센 코멘트 달아드렸는데 잘 반영해주셔서 고마워
점심 뭐먹지 미션도 이제 마지막을 향해가고 있네요~ 마지막까지 화이팅 👍🏻
> | ||
<option value="">선택해 주세요</option> | ||
{CATEGORY_DATA.slice(1).map((category) => ( | ||
<option key={category}>{category}</option> |
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.
key를 사용할 때 피해야 하는 상황을 잘 말씀해주셨네요 🚀
말씀해주신 예시인 [즉석에서 생성한 값을 전달] 의 경우에는 매 리렌더링마다 보통 새로운 값이 랜덤으로 생성되는 방법을 사용하기 때문에 React에서 key 값을 이용해 각 항목들을 식별하는 의미가 없어집니다. 항목이 계속해서 같은 키 값을 가지고 있어야 리스트의 여러 항목이 변경되더라도 그 항목을 식별하고 이를 이용해 더 최적화된 연산을 수행할 수 있는데, 매번 키 값이 새롭게 생성되면 React 입장에서는 실제로는 항목이 이동한 것에 불과한데도 새로운 항목이 추가된 것으로 인식하고 불필요한 연산들을 수행하려 할 겁니다.
index
를 키 값으로 사용하는 것도 비슷한 맥락입니다.
여기에 말씀하신 내용에 대해 좀 더 자세한 내용을 적어드릴게요.
리렌더링은 React.memo
등의 특수한 메모이제이션을 사용하지 않는 한 막을 수 없습니다. 리스트에서 부모 컴포넌트가 리렌더링되면 자식 컴포넌트는 리렌더링됩니다. 이건 기본 동작이자 원칙이며, key
값과 별개로 적용됩니다. 리렌더링이 되지 않으면 리스트를 지니고 있는 부모 컴포넌트의 상태가 바뀌어도 변경된 UI가 적용이 되지 않겠죠?
적절하게 key 값을 사용했을 때 작성하신 코드는 재조정(Reconcilation) 과정에서 활약할 수 있게 됩니다. 컴포넌트가 리렌더링되야 할 경우 React는 변경 사항을 비교하고 이를 기반으로 새로운 DOM 트리를 그려야 합니다. 이 때 key값을 기반으로 매번 트리를 새로 그리거나 새로운 DOM 요소를 비싼 비용으로 추가하지 않고 기존에 존재하는 DOM을 재사용할 수 있게 됩니다.
import { useState } from "react"; | ||
|
||
function AddRestaurantModal({ setIsAddModalOpen }) { | ||
const [selectedCategory, setSelectedCategory] = useState(""); | ||
function AddRestaurantModal({ setIsAddModalOpen, handleAddRestaurant }) { |
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.
handleAddRestaurant
깔끔하고 직관적인 이름이네요 👁️
@@ -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)} |
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.
🔨 ✅ 👍🏻
src/components/RestaurantList.jsx
Outdated
const handleRestaurantClick = (restaurant) => { | ||
setModal({ | ||
...modal, | ||
isOpen: true, | ||
restaurant: { | ||
name: restaurant.name, | ||
description: restaurant.description, | ||
}, | ||
}); |
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.
오, 관심사가 비슷하다고 생각하신 건가요? 이번에 state를 묶어주셨군요 😄
안녕하세요!
이번 미션에서 추가 버튼을 누르면 음식점이 restaurantsData 배열에 추가되어 화면에 뜨도록 구현하는 게 재밌더라구요.
그리고
onChange={(selected) => setSelectedCategory(selected.target.value)}
이 부분을 작성하면서 작동 원리가 헷갈렸는데요.찾아보니
selected
가onChange
이벤트가 발생할 때 자동으로 전달되는 이벤트 객체,selected.target
은 이벤트가 발생한 DOM 요소(select 태그),selected.target.value
는 사용자가 select 태그에서 선택한 option의 value 속성 값을 나타낸다는 것을 알게되었어요.시간이 없어서 선택 미션인 재사용 가능한 Modal 컴포넌트 구현은 못했습니다😢
구현 내용
(Header.jsx)

Header의 음식점 추가 버튼을 누르면 음식점 추가 모달이 열립니다.
그리고 step3의 RestaurantDetailModal에서도 얘기한 거지만 여기서도 handleClick을 없애고
onClick={handleClick}
이 부분을onClick={() => SetIsModalOpen(false)}
이렇게 고치려고 합니다!(AddRestaurantModal.jsx)




(event.preventDefault()가 없어도 정상 동작되는데 이때 제가 헷갈렸나봐요..)
음식점 추가 과정:
구조
실행 영상
-.Chrome.2024-11-05.23-37-56.mp4
feat: 클릭한 음식점의 정보를 모달에 표시
feat: backdrop을 클릭하면 모달이 닫힘
이 부분이 step3,
feat: 음식점 추가 모달 열고 닫는 기능
feat: 모달의 추가 버튼을 누르면 레스토랑 목록에 추가
이 부분이 step4입니다!