JiSoo's Devlog
[Udemy React 완벽 가이드] Section 7 본문
첫 번째 프로젝트
- App 컴포넌트를 여러 개의 작은 컴포넌트로 분할
- 이벤트 처리 - 폼 제출, 재설정 버튼 클릭, 사용자 입력 변경
- 상태 관리 - 관리해야 하는 상태 식별
- 상태를 이용해 화면에 데이터 출력
- 스타일링 - 스타일이 지정된 컴포넌트 사용하거나 모듈 CSS 파일로 분할하는 등
- '상태 올리기' 잊지 말기
앱을 컴포넌트로 분할하기
- App 컴포넌트를 Header, ResultsTable, UserInput 컴포넌트로 분할
- App 컴포넌트에 분할한 컴포넌트 작성하고 임포트
이벤트 처리하기
- 제출 이벤트 -> onSubmit prop을 내장된 form 컴포넌트에 추가, 제출 실행하는 함수 생성
- 리액트에서 form 제출을 처리할 때 기본값을 방지하고 싶은 경우가 많다 -> event 인수 수락해 event.preventDefault();
- 기본 동작을 방지해 페이지가 다시 로드되지 않도록 한다
- 여러 input에 change 이벤트를 처리해 주기 위해 제네릭 이벤트 핸들러 함수인 inputChangeHandler 을 만들고 인수에 두 개의 식별자를 넣어야 한다
- 첫 번째 식별자는 이벤트 소스를 식별해야 하는데 이벤트 소스는 이벤트가 발생한 input이다
- 두 번째 식별자는 입력된 값이어야 한다
- inputChangeHandler는 onChange prop과 함께 사용할 수 없는데 설정할 수는 있지만 정보를 얻지는 못한다
input onChange={()=>inputChangeHandler()}
- 화살표 함수가 실행될 때만 함수 실행 -> 입력값이 변경될 때
- inputChangeHandler의 괄호에 들어가는 첫 번째 인수는 input의 식별자여야 하고 두 번째 인수는 입력된 값이다
input onChange={(event)=>inputChangeHandler('current-savings', event.target.value)}
상태 관리하기
- 먼저 관리해야 할 상태 식별하기
UserInput.js
import { prettyDOM } from "@testing-library/react";
import { useState } from "react";
const initialUserInput = {
"current-savings": 10000,
"yearly-contribution": 1200,
"expected-return": 7,
duration: 10,
};
const UserInput = () => {
const [userInput, setUserInput] = useState(initialUserInput);
const submitHandler = (event) => {
event.preventDefault();
console.log("submit");
};
const resetHandler = () => {
setUserInput(initialUserInput);
};
const inputChangeHandler = (input, value) => {
setUserInput((preventInput) => {
return {
...preventInput,
[input]: +value,
};
});
};
return (
<form onSubmit={submitHandler} className="form">
<div className="input-group">
<p>
<label htmlFor="current-savings">Current Savings ($)</label>
<input
onChange={(event) =>
inputChangeHandler("current-savings", event.target.value)
}
value={userInput["current-savings"]}
type="number"
id="current-savings"
/>
</p>
<p>
<label htmlFor="yearly-contribution">Yearly Savings ($)</label>
<input
onChange={(event) =>
inputChangeHandler("yearly-contribution", event.target.value)
}
value={userInput["yearly-contribution"]}
type="number"
id="yearly-contribution"
/>
</p>
</div>
<div className="input-group">
<p>
<label htmlFor="expected-return">
Expected Interest (%, per year)
</label>
<input
onChange={(event) =>
inputChangeHandler("expected-return", event.target.value)
}
value={userInput["expected-return"]}
type="number"
id="expected-return"
/>
</p>
<p>
<label htmlFor="duration">Investment Duration (years)</label>
<input
onChange={(event) =>
inputChangeHandler("duration", event.target.value)
}
value={userInput["duration"]}
type="number"
id="duration"
/>
</p>
</div>
<p className="actions">
<button onClick={resetHandler} type="reset" className="buttonAlt">
Reset
</button>
<button type="submit" className="button">
Calculate
</button>
</p>
</form>
);
};
export default UserInput;
상태 올리기
UserInput.js
const UserInput = (props) => {
const [userInput, setUserInput] = useState(initialUserInput);
const submitHandler = (event) => {
event.preventDefault();
props.onCalculate(userInput);
};
App.js
import { useState } from "react";
import Header from "./Components/Header/Header";
import ResultsTable from "./Components/ResultsTable/ResultsTable";
import UserInput from "./Components/UserInput/UserInput";
function App() {
const [userInput, setUserInput] = useState(null);
const calculateHandler = (userInput) => {
setUserInput(userInput);
};
const yearlyData = [];
if (userInput) {
let currentSavings = +userInput["current-savings"];
const yearlyContribution = +userInput["yearly-contribution"];
const expectedReturn = +userInput["expected-return"] / 100;
const duration = +userInput["duration"];
for (let i = 0; i < duration; i++) {
const yearlyInterest = currentSavings * expectedReturn;
currentSavings += yearlyInterest + yearlyContribution;
yearlyData.push({
year: i + 1,
yearlyInterest: yearlyInterest,
savingsEndOfYear: currentSavings,
yearlyContribution: yearlyContribution,
});
}
}
return (
<div>
<Header />
<UserInput onCalculate={calculateHandler} />
<ResultsTable />
</div>
);
}
export default App;
조건에 따라 결과 출력하기
- 결과가 있는 경우에만 ResultsTable을 표시
const formatter = new Intl.NumberFormat("en-US", { //숫자 형식 지정
style: "currency",
currency: "USD",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
const ResultsTable = (props) => {
return (
<table className="result">
<thead>
<tr>
<th>Year</th>
<th>Total Savings</th>
<th>Interest (Year)</th>
<th>Total Interest</th>
<th>Invested Capital</th>
</tr>
</thead>
<tbody>
{props.data.map((yearData) => (
<tr key={yearData.year}>
<td>{yearData.year}</td>
<td>{formatter.format(yearData.savingEndOfYear)}</td>
<td>{formatter.format(yearData.yearlyInterest)}</td>
<td>
{formatter.format(
yearData.savingEndOfYear -
props.initialInvestment -
yearData.yearlyContribution * yearData.year
)}
</td>
<td>
{formatter.format(
props.initialInvestment +
yearData.yearlyContribution * yearData.year
)}
</td>
</tr>
))}
</tbody>
</table>
);
};
export default ResultsTable;
CSS 모듈 추가하기
- 스타일이 적용되는 방식 바꾸기
- 인라인 방식 사용할 때 이중 중괄호를 하는 이유는 style prop에 전달된 값이 자바스크립트 객체이기 때문이다
import classes from "./Header.module.css";
const Header = () => {
return (
<header className={classes.header}>
- Header.module.css로 파일을 분리해 주고 클래스명 변경, 임포트
return (
<form onSubmit={submitHandler} className={classes.form}>
<div className={classes["input-group"]}>
import React from "react";
import classes from "./Button.module.css";
const Button = (props) => {
return (
<button
className={classes.button}
type={props.type || "button"}
onclick={props.onClick}
>
{props.children}
</button>
);
};
export default Button;
두 번째 프로젝트
- AddUser
- UsersList
- Button
- Card
- ErrorModal
- Button과 Card는 래퍼 컴포넌트
'사용자' 컴포넌트 추가하기
- htmlFor="" 가 label에 for의 속성을 할당하는 props의 이름이다
<label htmlFor="username">Username</label> //input에 연결
<input id="username" type="text" />
재사용 가능한 '카드' 컴포넌트 추가하기
import React from "react";
import classes from "./Card.module.css";
const Card = (props) => {
return (
<div className={`${classes.card} ${props.className}`}>{props.children}</div>
);
};
export default Card;
재사용 가능한 'Button' 컴포넌트 추가하기
사용자 입력 State 관리하기
import React, { useState } from "react";
import Card from "../UI/Card";
import Button from "../UI/Button";
import classes from "./AddUser.module.css";
const AddUser = (props) => {
const [enteredUsername, setEnteredUsername] = useState("");
const [enteredAge, setEnteredAge] = useState("");
const addUserHandler = (event) => {
event.preventDefault();
console.log(enteredUsername, enteredAge);
};
const usernameChangeHandler = (event) => {
setEnteredUsername(event.target.value);
};
const ageChangeHandler = (event) => {
setEnteredAge(event.target.value);
};
return (
<Card className={classes.input}>
<form onSubmit={addUserHandler}>
<label htmlFor="username">Username</label>
<input id="username" type="text" onChange={usernameChangeHandler} />
<label htmlFor="age">Age (Years)</label>
<input id="age" type="number" onChange={ageChangeHandler} />
<Button type="submit">Add User</Button>
</form>
</Card>
);
};
export default AddUser;
검증 추가 및 로직 재설정하기
const addUserHandler = (event) => {
event.preventDefault();
console.log(enteredUsername, enteredAge);
setEnteredUsername("");
setEnteredAge("");
};
~~~~
return (
<Card className={classes.input}>
<form onSubmit={addUserHandler}>
<label htmlFor="username">Username</label>
<input
id="username"
type="text"
value={enteredUsername} //value 추가
onChange={usernameChangeHandler}
/>
const addUserHandler = (event) => {
event.preventDefault();
if (enteredUsername.trim().length === 0 || enteredAge.trim().length === 0) {
return;
}
if(+enteredAge < 1){ //유효성 검사
return;
}
console.log(enteredUsername, enteredAge);
setEnteredUsername("");
setEnteredAge("");
};
사용자 목록 컴포넌트 추가하기
UsersList.js
import React from "react";
import classes from "./UsersList.module.css";
import Card from "../UI/Card";
const UsersList = (props) => {
return (
<Card className={classes.users}>
<ul>
{props.users.map((user) => (
<li>
{user.name} ({user.age} years old)
</li>
))}
</ul>
</Card>
);
};
export default UsersList;
App.js
import React from "react";
import AddUser from "./components/Users/AddUser";
import UsersList from "./components/Users/UsersList";
function App() {
return (
<div>
<AddUser />
<UsersList users={[]} />
</div>
);
}
export default App;
State를 통해 사용자 목록 관리하기
function App() {
const [usersList, setUsersList] = useState([]);
const addUserHandler = (uName, uAge) => {
setUsersList((prevUsersList) => {
return [...prevUsersList, { name: uName, age: uAge, id:Math.random().toString() }];
});
};
return (
<div>
<AddUser onAddUser={addUserHandler} />
<UsersList users={usersList} />
</div>
);
}
AddUser.js
const AddUser = (props) => {
const [enteredUsername, setEnteredUsername] = useState("");
const [enteredAge, setEnteredAge] = useState("");
const addUserHandler = (event) => {
event.preventDefault();
if (enteredUsername.trim().length === 0 || enteredAge.trim().length === 0) {
return;
}
if (+enteredAge < 1) {
return;
}
props.onAddUser(enteredUsername, enteredAge);
setEnteredUsername("");
setEnteredAge("");
};
'ErrorModal' 컴포넌트 추가하기
ErrorModal.js
import React from "react";
import Card from "./Card";
import Button from "./Button";
import classes from "./ErrorModal.module.css";
const ErrorModal = (props) => {
return (
<div>
<div className={classes.backdrop} /> //어떤 폼과도 상호작용 X
<Card className={classes.modal}>
<header className={classes.header}>
<h2>{props.title}</h2>
</header>
<div className={classes.content}>
<p>{props.message}</p>
</div>
<footer className={classes.actions}>
<Button>Okay</Button>
</footer>
</Card>
</div>
);
};
export default ErrorModal;
오류 State 관리하기
- 오류 상태 관리 필요
event.preventDefault();
if (enteredUsername.trim().length === 0 || enteredAge.trim().length === 0) {
setError({
title: "Invalid input",
message: "Please enter a valid name and age (non-empty values).",
});
return;
}
if (+enteredAge < 1) {
setError({
title: "Invalid age",
message: "Please enter a valid age (> 0).",
});
return;
}
~~~~
return (
<div>
{error && <ErrorModal title={error.title} message={error.message} />}
<Card className={classes.input}>
~~~~
- 모달창을 없애는 유일한 방법은 Error를 undefined나 null 또는 다른 falsy 값으로 초기화하는 것
728x90
'Frontend > React' 카테고리의 다른 글
[Udemy React 완벽 가이드] Section 10 (2) | 2024.01.04 |
---|---|
[Udemy React 완벽 가이드] Section 8 (2) | 2024.01.04 |
[Udemy React 완벽 가이드] Section 6 (2) | 2024.01.04 |
[Udemy React 완벽 가이드] Section 5 (2) | 2024.01.04 |
[Udemy React 완벽 가이드] Section 4 (2) | 2024.01.04 |