JiSoo's Devlog

[Udemy React 완벽 가이드] Section 7 본문

Frontend/React

[Udemy React 완벽 가이드] Section 7

지숭숭숭 2024. 1. 4. 11:41

 

첫 번째 프로젝트

  • 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