📘 [Recat] React 핵심 - 연습 프로젝트
![📘 [Recat] React 핵심 - 연습 프로젝트](/assets/img/thumbnail/react-thumbnail.jpg)
[Recat] React 핵심 - 연습 프로젝트
연습 프로젝트는 Investment Calculator을 구현해본다.
src/components/Header.jsx
파일 생성
Header
컴포넌트 작성
// Header.jsx
// logo 이미지를 불러온다.
import logo from '../assets/investment-calculator-logo.png';
export default function Header() {
return (
<header id="header">
<img src={logo} alt="Logo showing a money bag" />
<h1>Investment Calculator</h1>
</header>
);
}
App.jsx
에서 Header 컴포넌트 사용
import Header from './components/Header.jsx';
function App() {
return (
<>
<Header />
{/* 나머지 컴포넌트 */}
</>
);
}
export default App;
- Initial Investment (초기 투자금)
- Annual Investment (연간 투자금)
- Expected Return (예상 수익률)
- Duration (투자 기간)
투자 관련 정보를 입력받기 위한 컴포넌트 만들기
src/components/UserInput.jsx
파일 생성
// UserInput.jsx
export default function UserInput() {
return (
<section id="user-input">
<div className="input-group">
<p>
<label>Initial Investment</label>
<input type="number" required />
</p>
<p>
<label>Annual Investment</label>
<input type="number" required />
</p>
</div>
<div className="input-group">
<p>
<label>Expected Return</label>
<input type="number" required />
</p>
<p>
<label>Duration</label>
<input type="number" required />
</p>
</div>
</section>
);
}
App.jsx
에서 UserInput 컴포넌트 사용
import Header from './components/Header.jsx';
function App() {
return (
<>
<Header />
<UserInput />
{/* 추후 결과 테이블 컴포넌트 추가 예정 */}
</>
);
}
export default App;
4개의 투자 관련 입력 필드를 리액트 상태로 관리
상태 초기화
import { useState } from 'react';
const [userInput, setUserInput] = useState({
initialInvestment: 10000,
annualInvestment: 1200,
expectedReturn: 6,
duration: 10,
});
입력 변경 함수(input 입력받은 속성만 업데이트 불변성 유지)
function handleChange(inputIdentifier, newValue) {
setUserInput((prevInput) => {
return {
...prevInput,
[inputIdentifier]: +newValue, // 문자열을 숫자로 변환
};
});
}
각 입력 요소 구성
export default function UserInput({ onChange, userInput }) {
return (
<input
type="number"
value={userInput.initialInvestment}
onChange={(e) => onChange('initialInvestment', e.target.value)}
/>
);
}
onChange
→ 익명 함수로 이벤트 객체 수신- 입력 필드의 value는 상태값으로 설정 (양방향 바인딩)
- 동일한 방식으로 나머지 3개 input도 구성
App.jsx
에서 UserInput, Result컴포넌트 연결
<UserInput onChange={handleChange} userInput={userInput} />
<Result input={userInput} />
투자 결과 보여주기
util/investment.js
생성 calculateInvestmentResults(input)
함수를 이용하여 투자 결과 계산
// This function expects a JS object as an argument
// The object should contain the following properties
// - initialInvestment: The initial investment amount
// - annualInvestment: The amount invested every year
// - expectedReturn: The expected (annual) rate of return
// - duration: The investment duration (time frame)
export function calculateInvestmentResults({
initialInvestment,
annualInvestment,
expectedReturn,
duration,
}) {
const annualData = [];
let investmentValue = initialInvestment;
for (let i = 0; i < duration; i++) {
const interestEarnedInYear = investmentValue * (expectedReturn / 100);
investmentValue += interestEarnedInYear + annualInvestment;
annualData.push({
year: i + 1, // year identifier
interest: interestEarnedInYear, // the amount of interest earned in this year
valueEndOfYear: investmentValue, // investment value at end of year
annualInvestment: annualInvestment, // investment added in this year
});
}
return annualData;
}
// The browser-provided Intl API is used to prepare a formatter object
// This object offers a "format()" method that can be used to format numbers as currency
// Example Usage: formatter.format(1000) => yields "$1,000"
export const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
});
src/components/Result.jsx
파일 생성 calculateInvestmentResults(input)
함수로 얻은 결과 데이터를 HTML <table>
로 출력
import { calculateInvestmentResults, formatter } from "../util/investment";
export default function Result({ input }) {
console.log(input);
const resultsData = calculateInvestmentResults(input);
console.log(resultsData);
const initialInvestment =
resultsData[0].valueEndOfYear -
resultsData[0].interest -
resultsData[0].annualInvestment;
return (
<table id="result">
<thead>
<tr>
<th>Year</th>
<th>Investmeth Value</th>
<th>Interest(Year)</th>
<th>Total Interest</th>
<th>Invested Capital</th>
</tr>
</thead>
<tbody>
{resultsData.map((yearData) => {
const totalInterest =
yearData.valueEndOfYear -
yearData.annualInvestment * yearData.year -
initialInvestment;
const totalAmountInvested = yearData.valueEndOfYear - totalInterest;
return (
<tr key={yearData.year}>
<td>{formatter.format(yearData.year)}</td>
<td>{formatter.format(yearData.valueEndOfYear)}</td>
<td>{formatter.format(yearData.interest)}</td>
<td>{formatter.format(totalInterest)}</td>
<td>{formatter.format(yearData.totalAmountInvested)}</td>
</tr>
);
})}
</tbody>
</table>
);
}
formatter.format()
은 investment.js
에서 가져온 포맷터로 금액을 보기 좋게 출력합니다.
마무리 단계 유효성 체크
duration
을 0이나 음수로 입력하면 calculateInvestmentResults
함수가 잘못 작동 다른 값은 음수도 허용할 수 있지만, duration
은 반드시 1 이상의 양수여야 의미 있는 결과 도출이 가능 duration
음수로 입력시 에러 메세지 표시
const inputIsValid = userInput.duration >= 1;
{!inputIsValid && (
<p className="center">Please enter a duration greater than 0.</p>
)}
duration
1보다 작을경우 Please enter a duration greater than 0. 출력