-
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
[2단계 미션] 배강현 미션 제출합니다 #25
base: bae-kh
Are you sure you want to change the base?
Changes from all commits
02f16cc
f6ebc31
80042c5
4337856
9b30b6f
a507d5c
5101e36
b0cb37c
18d97e6
857ffea
e088ed9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
* { | ||
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; | ||
} | ||
|
||
/* Typography *************************************/ | ||
.text-title { | ||
font-size: 20px; | ||
line-height: 24px; | ||
font-weight: 600; | ||
} | ||
|
||
.text-subtitle { | ||
font-size: 18px; | ||
line-height: 28px; | ||
font-weight: 600; | ||
} | ||
|
||
.text-body { | ||
font-size: 16px; | ||
line-height: 24px; | ||
font-weight: 400; | ||
} | ||
|
||
.text-caption { | ||
font-size: 14px; | ||
line-height: 20px; | ||
font-weight: 400; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,39 @@ | ||
import "./App.css"; | ||
import Header from "./header/Header.jsx"; | ||
import CategoryFilter from "./mainbody/CategoryFilter.jsx"; | ||
import RestaurantList from "./mainbody/RestaurantList.jsx"; | ||
import RestaurantDetailModal from "./aside/RestaurantDetailModal.jsx"; | ||
import AddRestaurantModal from "./aside/AddRestaurantModal.jsx"; | ||
import { useState } from "react"; | ||
import { restaurants } from "./datas/RestaurantData"; | ||
|
||
function App() { | ||
return <h1>Self-Paced React</h1>; | ||
const [category,setCategory] = useState("전체"); | ||
|
||
const filteredRestaurants= category==="전체" ? restaurants : restaurants.filter((restaurant)=>restaurant.category===category); | ||
|
||
const handleCategoryChange = (newCategory) => { | ||
console.log("Selected category:", newCategory); // 카테고리가 변경될 때 출력 | ||
setCategory(newCategory); // 실제 카테고리 상태 변경 | ||
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. category = newCategory; 위와 같은 형태의 코드를 사용하지 않으시고, setter 함수를 통해 상태 값을 변경하신 이유는 무엇인가요? 위 방법은 작동할까요? 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. setter함수를 사용할 경우 상태가 변경될 때마다 컴포넌트를 렌더링하도록 해주기 때문에 사용했습니다. 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.
정확합니다. 이렇게 명시된 JavaScript에서는 |
||
}; | ||
|
||
console.log(filteredRestaurants); | ||
return ( | ||
<> | ||
<Header /> | ||
<main> | ||
|
||
<CategoryFilter category={category} onChangeCategory={handleCategoryChange} /> | ||
<RestaurantList restaurants={filteredRestaurants} /> | ||
|
||
</main> | ||
<aside> | ||
<RestaurantDetailModal /> | ||
<AddRestaurantModal /> | ||
</aside> | ||
</> | ||
); | ||
} | ||
|
||
|
||
export default App; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import styles from "../css/RestaurantDetailModal.module.css"; | ||
import { options } from "../datas/RestaurantData"; | ||
function AddRestaurantModal() { | ||
return ( | ||
<div className={`${styles.modal} ${styles.modalOpen}`}> | ||
<div className={styles.modalBackdrop}></div> | ||
<div className={styles.modalContainer}> | ||
<h2 className={`${styles.modalTitle} text-title`}>새로운 음식점</h2> | ||
<form> | ||
{/* <!-- 카테고리 --> */} | ||
<div className={`${styles.formItem} ${styles.formItemRequired}`}> | ||
<label htmlFor="category" className="text-caption">카테고리</label> | ||
<select name="category" id="category" required> | ||
{options.map((option, index) => ( | ||
<option key={index} value={option}> | ||
{option} | ||
</option> | ||
))} | ||
</select> | ||
</div> | ||
|
||
{/* <!-- 음식점 이름 --> */} | ||
<div className={`${styles.formItem} ${styles.formItemRequired}`}> | ||
<label htmlFor="name" className="text-caption">이름</label> | ||
<input type="text" name="name" id="name" required /> | ||
</div> | ||
|
||
{/* <!-- 설명 --> */} | ||
<div className={styles.formItem}> | ||
<label htmlFor="description" className="text-caption">설명</label> | ||
<textarea name="description" id="description" cols="30" rows="5"></textarea> | ||
<span className="help-text text-caption">메뉴 등 추가 정보를 입력해 주세요.</span> | ||
</div> | ||
|
||
{/* <!-- 추가 버튼 --> */} | ||
<div className={styles.buttonContainer}> | ||
<button className={`${styles.button} ${styles.buttonPrimary} text-caption`}> | ||
추가하기 | ||
</button> | ||
</div> | ||
</form> | ||
</div> | ||
</div> | ||
); | ||
} | ||
Comment on lines
+3
to
+45
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. 컴포넌트의 구조가 점점 커져가는 것이 느껴지시나요? 강현님께서 한창 숫자야구의 여러 함수를 구현하실 때, 하나의 함수가 하는 일이 커지면 이를 여러 개의 작은 함수로 나눠 각 함수가 자신이 맡은 하나의 일만 잘 수행하도록 구현해주신 적이 있으실 거에요. 컴포넌트도 마찬가지랍니다. 이제 강현님께서는 state와 props를 사용하실 수 있고, 비슷하게 생긴 UI가 보인다면 이들의 장점을 이용해 비슷한 UI를 두 개의 다른 컴포넌트로 구현하는 대신, state와 props만 바꿔서 이 비슷한 UI를 만들도록 구현하실 수 있게 되었어요. 슬슬 컴포넌트의 장점을 활용해 보시면 좋을 것 같습니다. 물론 때로는 이미 구조가 깔끔한데 과도하게 나누려는 접근 방법이 더 복잡할 수도 있어요. 그러니 "지금 구조는 깔끔한데? 나누지 않을래." 라는 결론을 내리시는 것도 좋아요. 대신 구조에 대해 충분히 고민해 보신 후 이유를 동반한 결정을 내리신다면 좋을 것 같습니다! 아직 미션은 더 남았으니, 지금 이 피드백은 반영하지 않으셔도 좋고 이후 단계에서 컴포넌트를 나눠볼 지, 아니면 유지해 볼 지를 고민해 보세요. 그리고 저 (또는 메인테이너님) 에게 그 생각을 공유해 보시는 것도 좋은 경험이 될 것 같아요. 나누고는 싶은데 뭘 나눠야 할 지 도저히 모르겠어! 라는 생각이라면, 반복해서 사용되는 UI, 굉장히 비슷하게 생긴 UI 를 유심히 살펴 보세요. 재사용을 하기 좋은 청신호일 수 있을 테니까요...! 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. 네넵 감사합니다!! 더 고민해보겠습니다 |
||
|
||
export default AddRestaurantModal; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import styles from "../css/RestaurantDetailModal.module.css"; | ||
|
||
function RestaurantDetailModal() { | ||
return ( | ||
<div className={`${styles.modal} ${styles["modal--open"]}`}> | ||
<div className={styles["modal-backdrop"]}></div> | ||
<div className={styles["modal-container"]}> | ||
<h2 className={`${styles["modal-title"]} text-title`}>음식점 이름</h2> | ||
<div className={styles["restaurant-info"]}> | ||
<p className={`${styles["restaurant-info__description"]} text-body`}> | ||
음식점 소개 문구 | ||
</p> | ||
</div> | ||
|
||
<div className={styles["button-container"]}> | ||
<button className={`${styles.button} ${styles["button--primary"]} text-caption`}> | ||
닫기 | ||
</button> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default RestaurantDetailModal; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
.restaurant-filter-container { | ||
display: flex; | ||
justify-content: space-between; | ||
|
||
padding: 0 16px; | ||
margin-top: 24px; | ||
} | ||
|
||
.restaurant-filter-container select { | ||
height: 44px; | ||
min-width: 125px; | ||
|
||
border: 1px solid #d0d5dd; | ||
border-radius: 8px; | ||
background: transparent; | ||
|
||
font-size: 16px; | ||
} | ||
|
||
.restaurant-filter { | ||
padding: 8px; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
.gnb { | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
height: 64px; | ||
|
||
padding: 0 16px; | ||
|
||
background-color: var(--primary-color); | ||
} | ||
|
||
.gnb__title { | ||
color: #fcfcfd; | ||
} | ||
|
||
.gnb__button { | ||
height: 40px; | ||
|
||
border: none; | ||
border-radius: 8px; | ||
background: transparent; | ||
|
||
font-size: 24px; | ||
cursor: pointer; | ||
} | ||
|
||
.gnb__button img { | ||
display: block; | ||
width: 40px; | ||
height: 40px; | ||
object-fit: contain; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
.modal { | ||
display: none; | ||
} | ||
|
||
.modal--open { | ||
display: none; | ||
} | ||
|
||
.modal-backdrop { | ||
position: fixed; | ||
top: 0; | ||
right: 0; | ||
bottom: 0; | ||
left: 0; | ||
|
||
background: rgba(0, 0, 0, 0.35); | ||
} | ||
|
||
.modal-container { | ||
position: fixed; | ||
bottom: 0; | ||
width: 100%; | ||
|
||
padding: 32px 16px; | ||
|
||
border-radius: 8px 8px 0px 0px; | ||
background: var(--grey-100); | ||
} | ||
|
||
.modal-title { | ||
margin-bottom: 36px; | ||
} | ||
|
||
.form-item { | ||
display: flex; | ||
flex-direction: column; | ||
|
||
margin-bottom: 36px; | ||
} | ||
|
||
.form-item label { | ||
color: var(--grey-400); | ||
font-size: 14px; | ||
} | ||
|
||
.form-item--required label::after { | ||
padding-left: 4px; | ||
|
||
color: var(--primary-color); | ||
content: "*"; | ||
} | ||
|
||
.form-item .help-text { | ||
color: var(--grey-300); | ||
} | ||
|
||
.form-item input, | ||
.form-item textarea, | ||
.form-item select { | ||
padding: 8px; | ||
margin: 6px 0; | ||
|
||
border: 1px solid var(--grey-200); | ||
border-radius: 8px; | ||
|
||
font-size: 16px; | ||
} | ||
|
||
.form-item textarea { | ||
resize: none; | ||
} | ||
|
||
.form-item select { | ||
height: 44px; | ||
|
||
padding: 8px; | ||
|
||
border: 1px solid var(--grey-200); | ||
border-radius: 8px; | ||
|
||
color: var(--grey-300); | ||
} | ||
|
||
input[name="name"], | ||
input[name="link"] { | ||
height: 44px; | ||
} | ||
|
||
.button-container { | ||
display: flex; | ||
} | ||
|
||
.button { | ||
width: 100%; | ||
height: 44px; | ||
|
||
margin-right: 16px; | ||
|
||
border: none; | ||
border-radius: 8px; | ||
|
||
font-weight: 600; | ||
cursor: pointer; | ||
} | ||
|
||
.button:last-child { | ||
margin-right: 0; | ||
} | ||
|
||
.button--secondary { | ||
border: 1px solid var(--grey-300); | ||
background: transparent; | ||
|
||
color: var(--grey-300); | ||
} | ||
|
||
.button--primary { | ||
background: var(--primary-color); | ||
|
||
color: var(--grey-100); | ||
} | ||
|
||
.restaurant-info { | ||
margin-bottom: 24px; | ||
} |
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.
💡 인터뷰
props
와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.
부모 컴포넌트가 자식 컴포넌트에 값을 전달하기 때문에 단방향 데이터 흐름을 갖습니다.
props는 읽기 전용으로 자식컴포넌트에서 직접 수정할 수 없습니다다.
state는 컴포넌트 내부에서 관리하는 데이터로 변경 가능한 값입니다.
다른 컴포넌트와 공유되지 않고 컴포넌트의 동적인 부분을 관리하며, 값이 변경될 때마다 해당 컴포넌트는 자동으로 다시 렌더링됩니다.
props는 읽기 전용으로 자식 컴포넌트에서 변경이 불가하지만 state는 변경 가능합니다.
3.props는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하거나 상호작용을 위해 함수를 전달할 때 사용합니다. 예를 들어 부모가 자식에게 클릭 핸들러를 전달하여 자식이 클릭 이벤트를 부모에 알릴 수 있습니다.
state는 컴포넌트 내부에서 동적인 상태를 관리합니다. 예를 들어 사용자의 입력 값, 카운트 상태와 같은 내부 변경 사항을 관리하여 즉시 화면에 반영되도록 합니다.