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

[2단계 미션] 배강현 미션 제출합니다 #25

Open
wants to merge 11 commits into
base: bae-kh
Choose a base branch
from
20,200 changes: 17,202 additions & 2,998 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"start": "react-scripts start",
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
Expand All @@ -12,7 +13,8 @@
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-scripts": "^5.0.1"
},
"devDependencies": {
"@types/react": "^18.2.66",
Expand Down
52 changes: 52 additions & 0 deletions src/App.css
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;
}
34 changes: 33 additions & 1 deletion src/App.jsx

Choose a reason for hiding this comment

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

💡 인터뷰

  1. React에서 propsstate 는 무엇인가요?
  2. 둘의 차이점은 무엇인가요?
  3. 어떻게 활용할 수 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

  1. props는 부모 컴포넌트가 자식 컴포넌트에게 전달하는 데이터입니다.
    부모 컴포넌트가 자식 컴포넌트에 값을 전달하기 때문에  단방향 데이터 흐름을 갖습니다. 
    props는 읽기 전용으로 자식컴포넌트에서 직접 수정할 수 없습니다다.

state는 컴포넌트 내부에서 관리하는 데이터로 변경 가능한 값입니다.
다른 컴포넌트와 공유되지 않고 컴포넌트의 동적인 부분을 관리하며, 값이 변경될 때마다 해당 컴포넌트는 자동으로 다시 렌더링됩니다.

  1. 둘의 차이로는 props는 부모 컴포넌트가 관리하고 state는 컴포넌트 자신이 관리합니다.
    props는 읽기 전용으로 자식 컴포넌트에서 변경이 불가하지만 state는 변경 가능합니다.

3.props는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하거나 상호작용을 위해 함수를 전달할 때 사용합니다. 예를 들어 부모가 자식에게 클릭 핸들러를 전달하여 자식이 클릭 이벤트를 부모에 알릴 수 있습니다.

state는 컴포넌트 내부에서 동적인 상태를 관리합니다. 예를 들어 사용자의 입력 값, 카운트 상태와 같은 내부 변경 사항을 관리하여 즉시 화면에 반영되도록 합니다.

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); // 실제 카테고리 상태 변경

Choose a reason for hiding this comment

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

category = newCategory;

위와 같은 형태의 코드를 사용하지 않으시고, setter 함수를 통해 상태 값을 변경하신 이유는 무엇인가요? 위 방법은 작동할까요?

Copy link
Author

Choose a reason for hiding this comment

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

setter함수를 사용할 경우 상태가 변경될 때마다 컴포넌트를 렌더링하도록 해주기 때문에 사용했습니다.
위 코드 같이 작성할 경우는 상태가 변경될 때 감지하지 못해서 다시 렌더링하지 못할 수 있어서 작동하지 못할 것 같습니다.

Copy link

Choose a reason for hiding this comment

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

상태가 변경될 때 감지하지 못해서

정확합니다. 이렇게 명시된 setter 함수를 사용하는 이유는 React에게 상태가 변화했음을 알리고 이를 기반으로 상태가 변화했을 때 해야하는 적절한 조치들(리렌더링 등)을 수행하기 위해 사용합니다.

JavaScript에서는 category = newCategory 가 용인되겠지만 이렇게 라이브러리나 프레임워크를 사용하는 경우에는 해당 기술에서 제공하는 규칙들이 여럿 있으므로, 항상 공식 문서를 정독하는 습관이 중요합니다 📖

};

console.log(filteredRestaurants);
return (
<>
<Header />
<main>

<CategoryFilter category={category} onChangeCategory={handleCategoryChange} />
<RestaurantList restaurants={filteredRestaurants} />

</main>
<aside>
<RestaurantDetailModal />
<AddRestaurantModal />
</aside>
</>
);
}


export default App;
47 changes: 47 additions & 0 deletions src/aside/AddRestaurantModal.jsx
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

Choose a reason for hiding this comment

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

컴포넌트의 구조가 점점 커져가는 것이 느껴지시나요?

강현님께서 한창 숫자야구의 여러 함수를 구현하실 때, 하나의 함수가 하는 일이 커지면 이를 여러 개의 작은 함수로 나눠 각 함수가 자신이 맡은 하나의 일만 잘 수행하도록 구현해주신 적이 있으실 거에요. 컴포넌트도 마찬가지랍니다.

이제 강현님께서는 state와 props를 사용하실 수 있고, 비슷하게 생긴 UI가 보인다면 이들의 장점을 이용해 비슷한 UI를 두 개의 다른 컴포넌트로 구현하는 대신, state와 props만 바꿔서 이 비슷한 UI를 만들도록 구현하실 수 있게 되었어요. 슬슬 컴포넌트의 장점을 활용해 보시면 좋을 것 같습니다.

물론 때로는 이미 구조가 깔끔한데 과도하게 나누려는 접근 방법이 더 복잡할 수도 있어요. 그러니 "지금 구조는 깔끔한데? 나누지 않을래." 라는 결론을 내리시는 것도 좋아요. 대신 구조에 대해 충분히 고민해 보신 후 이유를 동반한 결정을 내리신다면 좋을 것 같습니다!

아직 미션은 더 남았으니, 지금 이 피드백은 반영하지 않으셔도 좋고 이후 단계에서 컴포넌트를 나눠볼 지, 아니면 유지해 볼 지를 고민해 보세요. 그리고 저 (또는 메인테이너님) 에게 그 생각을 공유해 보시는 것도 좋은 경험이 될 것 같아요.

나누고는 싶은데 뭘 나눠야 할 지 도저히 모르겠어! 라는 생각이라면, 반복해서 사용되는 UI, 굉장히 비슷하게 생긴 UI 를 유심히 살펴 보세요. 재사용을 하기 좋은 청신호일 수 있을 테니까요...!

Copy link
Author

Choose a reason for hiding this comment

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

네넵 감사합니다!! 더 고민해보겠습니다


export default AddRestaurantModal;
25 changes: 25 additions & 0 deletions src/aside/RestaurantDetailModal.jsx
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;
22 changes: 22 additions & 0 deletions src/css/CategoryFilter.module.css
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;
}
32 changes: 32 additions & 0 deletions src/css/Header.module.css
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;
}
125 changes: 125 additions & 0 deletions src/css/RestaurantDetailModal.module.css
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;
}
Loading