리액트 연습 프로젝트

·2024년 1월 22일

React

목록 보기
4/29

🔗 직접 작성한 코드 보기
🔗 강사 코드 보기

차후에 직접 작성한 코드는 수정이 있을 예정입니다!



📌 Header

Header.jsx

import logo from "../asset/investment-calculator-logo.png";

export default function Header() {
  return (
    <header id="header">
      <img src={logo} alt="logo image" />
      <h1>Investment Calculator</h1>
    </header>
  );
}

App.jsx

import Header from "./components/Header.jsx";
function App() {
  return <Header />;
}

export default App;

결과


📌 User Input(사용자 입력) 컴포넌트

UserInput.jsx

export default function UserInput() {
  return (
    <section id="user-input">
      <div className="input-group">
        <p>
          <label htmlFor="initial-investment">Initial Investment</label>
          <input type="number" id="initial-investment" required />
        </p>
        <p>
          <label htmlFor="annual-investment">Annual Investment</label>
          <input type="number" id="annual-investment" required />
        </p>
      </div>
      <div className="input-group">
        <p>
          <label htmlFor="expected-investment">Expected Investment</label>
          <input type="number" id="expected-investment" required />
        </p>
        <p>
          <label htmlFor="duration">Duration</label>
          <input type="number" id="duration" required />
        </p>
      </div>
    </section>
  );
}

📖 이벤트 핸들링 & 양방향 바인딩

  • 유저가 입력한 값을 얻고 저장하기 위해서는 해당 컴포넌트(UserInput)에서 몇가지 상태를 관리해야한다.

초기 상태 입력

import { useState } from "react";

export default function UserInput() {
  const [userInput, setUserInput] = useState({
    initialInvestment: 10000,   
    annualInvestment: 1200,
    expectedInvestment: 6,
    duration: 10,
  });
}

값이 변경될 때마다 작동될 함수 = 상태 업데이트 함수

  • 내가 직접 작성했을 때는 이부분을 놓쳤다. 이전 상태를 받아와야한다는 것을 알았지만, ...prevUserInput, [inputIdentifier]:newValue 부분에 대한 코드 작성에 익숙치 않아서 해당 부분을 떠올리지 못했다.
function handleInputChange(inputIdentifier, newValue) {
    setUserInput((prevUserInput) => {
      return {
        ...prevUserInput,
        [inputIdentifier]: newValue,
      };
    });
  }

UserInput 최종 코드

import { useState } from "react";

export default function UserInput() {
  const [userInput, setUserInput] = useState({
    initialInvestment: 10000,
    annualInvestment: 1200,
    expectedReturn: 6,
    duration: 10,
  });

  function handleInputChange(inputIdentifier, newValue) {
    setUserInput((prevUserInput) => {
      return {
        ...prevUserInput,
        [inputIdentifier]: newValue,
      };
    });
  }

  return (
    <section id="user-input">
      <div className="input-group">
        <p>
          <label htmlFor="initial-investment">Initial Investment</label>
          <input
            type="number"
            id="initial-investment"
            required
            value={userInput.initialInvestment}
            onChange={(e) =>
              handleInputChange("initialInvestment", +e.target.value)
            }
          />
        </p>
        <p>
          <label htmlFor="annual-investment">Annual Investment</label>
          <input
            type="number"
            id="annual-investment"
            required
            value={userInput.annualInvestment}
            onChange={(e) =>
              handleInputChange("annualInvestment", +e.target.value)
            }
          />
        </p>
      </div>
      <div className="input-group">
        <p>
          <label htmlFor="expected-investment">Expected Investment</label>
          <input
            type="number"
            id="expected-investment"
            required
            value={userInput.expectedReturn}
            onChange={(e) =>
              handleInputChange("expectedReturn", +e.target.value)
            }
          />
        </p>
        <p>
          <label htmlFor="duration">Duration</label>
          <input
            type="number"
            id="duration"
            required
            value={userInput.duration}
            onChange={(e) => handleInputChange("duration", +e.target.value)}
          />
        </p>
      </div>
    </section>
  );
}
  • 강사는 UserInput.jsx에 모든 label, input들을 넣었다. 그리고 해당 input에 valueonChange props를 추가했다.
  • value={userInput.~}를 이용해서 업데이트된 상태를 반영할 수 있도록 했다.
  • onChange에는 직접 inputIdentifier를 작성하고 화살표 함수를 이용해 이벤트 타겟의 값을 직접 전달했다.

App.jsx

import Header from "./components/Header.jsx";
import UserInput from "./components/UserInput.jsx";

function App() {
  return (
    <>
      <Header />
      <UserInput />
    </>
  );
}

export default App;

📌 State(상태) 끌어올리기

📖 calculateInvestmentResults 함수 이용하기

  • 해당 함수는 result 테이블에 출력해야하는 값을 리턴하므로 App.jsx에서 사용해야할다.
  • 그러기 위해선 UserInput.jsx에서 작성한 상태를 끌어올려야한다.

우선, Result.jsx를 작성한다.

export default function Results({ userInput }) {
  console.log(userInput)
  return <p>Results...</p>;
}
// 우선 정말 간단하게 틀만 잡는다.

App.jsx로 상태 끌어올리기

// App.jsx
import { useState } from "react"; // 상태 끌어올리기
import Header from "./components/Header.jsx";
import UserInput from "./components/UserInput.jsx";
import Results from "./components/Result.jsx";

function App() {
  // 상태 끌어올리기 - 상태 정의
  const [userInput, setUserInput] = useState({
    initialInvestment: 10000,
    annualInvestment: 1200,
    expectedReturn: 6,
    duration: 10,
  });

  // 상태 끌어올리기 - 상태 업데이트 함수
  function handleInputChange(inputIdentifier, newValue) {
    setUserInput((prevUserInput) => {
      return {
        ...prevUserInput,
        [inputIdentifier]: newValue,
      };
    });
  }

  return (
    <>
      <Header />
      {/* 상태 끌어올리기 - 상태 업데이트 함수, 상태를 UserInput에게 전달.*/}
      <UserInput onChange={handleInputChange} userInput={userInput} />
      {/* 상태 끌어올리기 - userInput을 통해서 결과를 계산 및 출력 */}
      <Results userInput={userInput}/>
    </>
  );
}

export default App;

UserInput.jsx

// 상태 끌어올리기 - App으로부터 받은 onChange함수와 userInput 받아오기
export default function UserInput({ onChange, userInput }) {
  return (
    <section id="user-input">
      <div className="input-group">
        <p>
          <label htmlFor="initial-investment">Initial Investment</label>
          <input
            type="number"
            id="initial-investment"
            required
            value={userInput.initialInvestment}
            // onChange("initialInvestment", +e.target.value)로 함으로써 App에서 정의된 상태 업데이트 함수 동작
            onChange={(e) => onChange("initialInvestment", +e.target.value)}
          />
        </p>
        <p>
          <label htmlFor="annual-investment">Annual Investment</label>
          <input
            type="number"
            id="annual-investment"
            required
            value={userInput.annualInvestment}
            onChange={(e) => onChange("annualInvestment", +e.target.value)}
          />
        </p>
      </div>
      <div className="input-group">
        <p>
          <label htmlFor="expected-investment">Expected Investment</label>
          <input
            type="number"
            id="expected-investment"
            required
            value={userInput.expectedReturn}
            onChange={(e) => onChange("expectedReturn", +e.target.value)}
          />
        </p>
        <p>
          <label htmlFor="duration">Duration</label>
          <input
            type="number"
            id="duration"
            required
            value={userInput.duration}
            onChange={(e) => onChange("duration", +e.target.value)}
          />
        </p>
      </div>
    </section>
  );
}

결과


📌 Results를 위한 데이터 도출하기

Result.jsx

import { calculateInvestmentResults, formatter } from "../util/investment.js";

export default function Results({ userInput }) {
  // App으로부터 받아온 userInput을 가지고 계산 -> resultsData 배열 받아옴
  const resultsData = calculateInvestmentResults(userInput);
  // 초기 투자 금액 계산.
  const initialInvestment =
    resultsData[0].valueEndOfYear -
    resultsData[0].interest -
    resultsData[0].annualInvestment;

  return (
    <table id="result">
      <thead>
        <tr>
          <th>Year</th>
          <th>Investment Value</th>
          <th>Interest (Year)</th>
          <th>Total Interest</th>
          <th>Capital</th>
        </tr>
      </thead>
      <tbody>
        {/* resultsData 배열을 가지고 map함수 -> 배열 요소 하나씩 계산 후 출력 */}
        {resultsData.map((yearData) => {
          // 총 이자.
          const totalInterest =
            yearData.valueEndOfYear -
            yearData.annualInvestment * yearData.year -
            initialInvestment;
          // 투자한 총 금액
          const totalAmountInvested = yearData.valueEndOfYear - totalInterest;

          return (
            <tr key={yearData.year}>
              <td>{yearData.year}</td>
              <td>{formatter.format(yearData.valueEndOfYear)}</td>
              <td>{formatter.format(yearData.interest)}</td>
              <td>{formatter.format(totalInterest)}</td>
              <td>{formatter.format(totalAmountInvested)}</td>
            </tr>
          );
        })}
      </tbody>
    </table>
  );
}

결과


📌 조건적 콘텐츠 출력

  • 오류 메시지 출력하기

App.jsx

function App() {
  // 조건적 콘텐츠 - 에러메시지 출력 (duration >= 1 이어야 함)
  const inputIsValid = userInput.duration >= 1;

  return (
    <>
    ...
      {/* 조건적 콘텐츠 출력 */}
      {!inputIsValid && <p className="center">기간은 0보다 커야합니다.</p>}
      {inputIsValid && <Results userInput={userInput} />}
    </>
  );
}

결과


📌 직접 작성한 코드와 비교해보기

Component 설계

  • 직접 작성한 코드는 Header를 index.jsx에 직접 작성했다. → 헤더가 동적으로 작동하지는 않기 때문임.
  • Input, Tr에 대한 커스텀 컴포넌트를 직접 만들었다. 커스텀 컴포넌트를 만들었다는 것은 나쁘지 않지만 오히려 커스텀으로 만드는 바람에 해당 프로젝트를 복잡하게 만듦.

굳이 필요하지 않다면 커스텀 컴포넌트로 인해서 프로젝트를 복잡하게 만들지 말자.


App.jsx 및 State 설계

  • App.jsx에 상태를 끌어올린 것은 괜찮았다.
  • 그러나 해당 상태에 대한 초기화에서 조금 꼬인 것 같다.
    1. 상태에 대한 초기값을 calculateInvestmentResults()함수 적용한 값으로 했다. 이는 프로젝트에 대한 스스로의 이해 부족으로 인해 생겼다. 이로 인해 프로젝트가 복잡하게 됨.
    2. 상태 업데이트 함수가 복잡하다. 커스텀 컴포넌트, 상태 초기값의 잘못된 설정으로 인해서 상태 업데이트 함수가 꼬였다.
    3. 상태 업데이트 함수가 이전 값들을 반영해야한다는 것을 인지하고 있었으나 어떻게 해야 이전 값들을 반영할 수 있는지 방법을 몰랐고 미숙했다.

Result.jsx

  • 위에서, 특히 App.jsx와 State로 인해 복잡해진 프로젝트로 인해 results 표현 값도 복잡해졌다.

전반적으로 프로젝트에 대한 이해를 한 뒤에 어떻게 설계를 하면 좋을지 간략한 설계도를 고민해보면서 만드는 것이 좋을 것 같다. 또한 자바스크립트 문법에 대해서 아직 어려움을 가지고 있으니, 해당 부분은 더 공부가 필요하다.

0개의 댓글