토이프로젝트

[토이프로젝트]대출이자계산기 만들어보기 - 2편 : Next.js로 웹 서비스 개발하기

  • -
반응형

지난 글에 이어 계산 기능 구현하기 바로 들어가보겠습니다.


화면에 보이는 계산하기 버튼을 눌러보겠습니다.

(좌) 프로젝트 / (우) 네이버 계산기

 

 

! 가 콘솔에 조회되고 있습니다.

다시 코드로 돌아와서 계산기 컴포넌트를 확인해볼까요?

(좌) 프로젝트 / (우) 네이버 계산기

 

 

버튼 컴포넌트에 onClick 이벤트로 console.log를 전달했고 정상적으로 실행이 된 것을 알 수 있습니다.

이제 이 버튼 컴포넌트를 클릭하면 계산이 될 수 있게 만들어 보겠습니다.

 

계산하기 버튼 클릭시 아래와 같이 팝업이 뜨며 계산 결과를 확인할 수 있게 할 것인데요. 우선 팝업을 추가해보겠습니다.

(좌) 프로젝트 / (우) 네이버 계산기

 

 

아이콘 라이브러리 추가

Lucide라는 아이콘 라이브러리를 추가하겠습니다.

npm i lucide-react

 

설치 후에는 사이트에서 원하는 아이콘 클릭 후 Copy JSX로 복사하고 붙여넣으면 바로 사용이 가능합니다.

(좌) 프로젝트 / (우) 네이버 계산기

 

팝업 추가

팝업을 만들기 위해 React Portal이라는 것을 이용할 것입니다. React Portal은 DOM 계층 구조 바깥에 있는 DOM 노드로 자식을 렌더링하는 최고의 방법을 제공합니다.

 

modal 컴포넌트를 생성해보겠습니다.

app/components/modal 디렉토리를 추가합니다. 디렉토리 안에 modal.tsx, modalPortal.tsx, modal.module.scss 파일도 추가합니다.

 

app/components/modal/modal.tsx

import { Dispatch, ReactNode, SetStateAction } from "react"; import { X } from "lucide-react"; import styles from "./modal.module.scss"; import ModalPortal from "./modalPortal"; type Props = { setToggleModal: Dispatch<SetStateAction<boolean>>; children: ReactNode; }; export default function Modal({ setToggleModal, children }: Props) { return ( <ModalPortal> <div className={styles.modal_wrap}> <div className={styles.modal_content}> <X className={styles.close_btn} onClick={() => setToggleModal(false)} /> {children} </div> </div> </ModalPortal> ); }

 

app/components/modal/modalPortal.tsx

import ReactDOM from "react-dom"; interface ModalPortalProps { children: React.ReactNode; } const ModalPortal = ({ children }: ModalPortalProps) => { const modalRoot = document.getElementById("modal") as HTMLElement; return ReactDOM.createPortal(children, modalRoot); }; export default ModalPortal;

 

app/components/modal/modal.module.scss

.modal_wrap { z-index: 99; height: 100%; width: 100%; display: flex; align-items: center; justify-content: center; position: fixed; left: 0; top: 0; text-align: center; background-color: rgba(0, 0, 0, 0.16); .close_btn { position: absolute; top: 20px; right: 20px; width: 35px; height: 35px; cursor: pointer; color: #333d4b; z-index: 999; } } .modal_content { position: relative; width: max-content; border-radius: 10px; background-color: white; overflow: hidden; }

 

모달 팝업이 생성될 위치를 추가하기 위해 layout.tsx를 수정합니다.

 

app/layout.tsx

import type { Metadata } from "next"; import "./globals.css"; import "./font.css"; export const metadata: Metadata = { title: "대출이자계산기", description: "간편한 대출이자계산기입니다.", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="ko"> <body> {children} <div id="modal"></div> </body> </html> ); }

 

팝업 실행하기

이제 추가한 팝업을 실행하기 위해 계산하기 버튼 이벤트를 수정해보겠습니다. 계산기 컴포넌트(form.tsx)로 돌아와 아래와 같이 코드를 수정합니다.

 

app/components/form/form.tsx

"use client"; import { FormProvider, useForm } from "react-hook-form"; import MyInput from "../input/input"; import MyButton from "../button/button"; import MyRadio from "../radio/radio"; import Modal from "../modal/modal"; import styles from "./form.module.scss"; import { useState } from "react"; const OPTIONS = [ { value: "1", name: "원리금 균등", }, { value: "2", name: "원금 균등", }, { value: "3", name: "만기 일시", }, ]; export default function Calcurator() { const [toggleModal, setToggleModal] = useState(false); const methods = useForm({ mode: "onChange", defaultValues: {}, }); return ( <FormProvider {...methods}> <MyRadio id={"paymentMethod"} options={OPTIONS} label={"상환방식"} /> <MyInput id={"amount"} label={"대출원금"} require={true} unit={"원"} /> <MyInput id={"period"} label={"대출기간"} require={true} unit={"년"} /> <MyInput id={"interest"} label={"연이자"} require={true} unit={"%"} /> <MyButton text={"계산하기"} onClick={() => setToggleModal(true)} /> {toggleModal && ( <Modal setToggleModal={setToggleModal}> <article className={styles.popup}> <div></div> </article> </Modal> )} </FormProvider> ); }

 

팝업 영역에 스타일도 추가합니다.

app/components/form/form.module.scss

.popup { margin-right: auto; margin-left: auto; padding: 50px 25px; overflow-y: auto; position: relative; width: 460px; max-width: 460px; height: 80vh; text-align: left; }

 

이제 F5를 눌러 실행해보겠습니다.

(좌) 프로젝트 / (우) 네이버 계산기

 

 

팝업 안에 조회되는 영역은 <div></div> 입니다. 이 영역을 컴포넌트로 분리시켜 계산 결과가 그려지도록 하겠습니다.

props를 통해 상환방식, 대출원금, 대출기간, 연이자 값을 전달해 계산 함수를 실행하면 될 것 같습니다.

(좌) 프로젝트 / (우) 네이버 계산기

 

그리드 추가

계산 결과가 그려질 그리드 컴포넌트를 만들어보겠습니다.

app/components/grid 디렉토리를 추가합니다. 디렉토리 안에 grid.tsx, grid.module.scss 파일도 추가합니다.

ul,li 태그를 사용하기 위해 리스트 스타일을 초기화해줍니다.

 

app/globals.css

a, li, ul { color: inherit; text-decoration: none; list-style: none; }

 

app/components/grid/grid.tsx

import styles from "./grid.module.scss"; const MyGrid = () => { return ( <div className={styles.grid_container}> <div className={styles.contents}> <ul className={styles.grid_header}> <li style={{ width: "10%" }}>회차</li> <li style={{ width: "22%" }}>상환원금</li> <li style={{ width: "22%" }}>이자액</li> <li style={{ width: "23%" }}>총납부액</li> <li style={{ width: "23%" }}>잔여원금</li> </ul> <ul className={styles.grid_body}> <li style={{ width: "10%", textAlign: "center" }}>1</li> <li style={{ width: "22%", textAlign: "right" }}>5000000</li> <li style={{ width: "22%", textAlign: "right" }}>500000</li> <li style={{ width: "23%", textAlign: "right" }}>5000000</li> <li style={{ width: "23%", textAlign: "right" }}>500000000</li> </ul> <ul className={styles.grid_body}> <li style={{ width: "10%", textAlign: "center" }}>2</li> <li style={{ width: "22%", textAlign: "right" }}>5000000</li> <li style={{ width: "22%", textAlign: "right" }}>500000</li> <li style={{ width: "23%", textAlign: "right" }}>5000000</li> <li style={{ width: "23%", textAlign: "right" }}>500000000</li> </ul> /div> </div> ); }; export default MyGrid;

 

app/components/grid/grid.module.scss

.grid_container { font-size: 15px; .grid_header { min-height: 55px; display: flex; padding: 0 10px; align-items: center; background: #f9fafb; margin-top: 20px; li { text-align: center; } } .grid_body { display: flex; align-items: center; padding: 0 10px; transition: background-color 0.2s ease; min-height: 45px; } }

 

추가한 그리드 컴포넌트를 계산기 컴포넌트에 추가하겠습니다.

 

app/components/form/form.tsx

"use client"; import { FormProvider, useForm } from "react-hook-form"; import MyInput from "../input/input"; import MyButton from "../button/button"; import MyRadio from "../radio/radio"; import Modal from "../modal/modal"; import styles from "./form.module.scss"; import { useState } from "react"; import MyGrid from "../grid/grid"; const OPTIONS = [ { value: "1", name: "원리금 균등", }, { value: "2", name: "원금 균등", }, { value: "3", name: "만기 일시", }, ]; export default function Calcurator() { const [toggleModal, setToggleModal] = useState(false); const methods = useForm({ mode: "onChange", defaultValues: {}, }); return ( <FormProvider {...methods}> <MyRadio id={"paymentMethod"} options={OPTIONS} label={"상환방식"} /> <MyInput id={"amount"} label={"대출원금"} require={true} unit={"원"} /> <MyInput id={"period"} label={"대출기간"} require={true} unit={"년"} /> <MyInput id={"interest"} label={"연이자"} require={true} unit={"%"} /> <MyButton text={"계산하기"} onClick={() => setToggleModal(true)} /> {toggleModal && ( <Modal setToggleModal={setToggleModal}> <article className={styles.popup}> <MyGrid /> </article> </Modal> )} </FormProvider> ); }

 

F5를 눌러 실행해보면 아래와 같은 화면이 확인됩니다. 이제 그리드 컴포넌트에서 props로 계산에 필요한 데이터를 전달 받고, 상환 방식에 따라 각 회차별(화살표 표시된 부분)로 금액이 계산되면 될 것 같습니다.

(좌) 프로젝트 / (우) 네이버 계산기

 

 

입력값 전달하기

그리드 컴포넌트에 입력값을 전달받기 위한 props를 정의하겠습니다.

 

app/components/grid/grid.tsx

import { useEffect, useState } from "react"; import styles from "./grid.module.scss"; export type GridType = { paymentMethod: string; amount: string; period: string; interest: string; }; type ResultType = { round: number; principalPayment: string; interest: string; monthlyPayment: string; remainPayment: string; }; type Props = { params: GridType | undefined; }; const MyGrid = ({ params }: Props) => { const [result, setResult] = useState<ResultType[]>(); useEffect(() => { console.log(params); }, []); return ( <div className={styles.grid_container}> <ul className={styles.grid_header}> <li style={{ width: "10%" }}>회차</li> <li style={{ width: "22%" }}>상환원금</li> <li style={{ width: "22%" }}>이자액</li> <li style={{ width: "23%" }}>총납부액</li> <li style={{ width: "23%" }}>잔여원금</li> </ul> {result?.map((obj, index) => { return ( <ul className={styles.grid_body} key={index}> <li style={{ width: "10%", textAlign: "center" }}>{obj.round}</li> <li style={{ width: "22%", textAlign: "right" }}>{obj.principalPayment}</li> <li style={{ width: "22%", textAlign: "right" }}>{obj.interest}</li> <li style={{ width: "23%", textAlign: "right" }}>{obj.monthlyPayment}</li> <li style={{ width: "23%", textAlign: "right" }}>{obj.remainPayment}</li> </ul> ); })} </div> ); }; export default MyGrid;

 

props로 전달받는 값의 타입을 GridType으로 정의합니다. GridType은 export해 계산기 컴포넌트에서 사용할 수 있게 합니다.

  • paymentMethod : 상환방식
  • amount : 대출원금
  • period : 대출기간
  • interest : 연 이자

 

ResultType은 계산 결과의 타입으로 사용됩니다. 

  • round : 회차
  • principalPayment : 상환원금
  • interest : 이자액
  • monthlyPayment : 총 납부액
  • remainPayment : 잔여원금

props로 전달받은 params를 이용해 계산을 실행하며, useEffect의 옵션(useEffect(() => {}, [])으로 최초 한번만 실행 되게 하겠습니다. 이제 계산기 컴포넌트에서 params를 전달해보겠습니다.

 

app/components/form/form.tsx

"use client"; import { FormProvider, useForm } from "react-hook-form"; import MyInput from "../input/input"; import MyButton from "../button/button"; import MyRadio from "../radio/radio"; import Modal from "../modal/modal"; import styles from "./form.module.scss"; import { useState } from "react"; import MyGrid, { GridType } from "../grid/grid"; const OPTIONS = [ { value: "1", name: "원리금 균등", }, { value: "2", name: "원금 균등", }, { value: "3", name: "만기 일시", }, ]; export default function Calcurator() { const [toggleModal, setToggleModal] = useState(false); const [params, setParams] = useState<GridType>(); const methods = useForm({ mode: "onChange", defaultValues: {}, }); const onSubmit = async (form: any) => { console.log(form); setParams({ paymentMethod: form.paymentMethod, amount: form.amount, period: form.period, interest: form.interest, }); setToggleModal(!toggleModal); }; const onInvalid = (errors: any) => { console.log(errors); if (errors && errors.amount) { alert(errors.amount.message); return; } else if (errors && errors.period) { alert(errors.period.message); return; } else if (errors && errors.interest) { alert(errors.interest.message); return; } }; return ( <FormProvider {...methods}> <MyRadio id={"paymentMethod"} options={OPTIONS} label={"상환방식"} /> <MyInput id={"amount"} label={"대출원금"} require={true} unit={"원"} /> <MyInput id={"period"} label={"대출기간"} require={true} unit={"년"} /> <MyInput id={"interest"} label={"연이자"} require={true} unit={"%"} /> <MyButton text={"계산하기"} onClick={methods.handleSubmit(onSubmit, onInvalid)} /> {toggleModal && ( <Modal setToggleModal={setToggleModal}> <article className={styles.popup}> <MyGrid params={params} /> </article> </Modal> )} </FormProvider> ); }

 

params를 관리할 useState와 onSumbmit, onInvalid 함수가 추가됐습니다.

react-hook-form을 사용하는 이유가 바로 여기서 나오는데요. 버튼 컴포넌트의 이벤트로 method.handleSubmit(onSubmit, onInvalid)가 전달됩니다.

작동 원리를 간략히 살펴보면, handleSubmit 메소드를 사용해 클릭 이벤트를 처리하면 onSubmit, onInvalid 콜백 함수를 전달할 수 있습니다. (여기서 함수명은 달라도 상관 없습니다) 이 콜백 함수들은 자동으로 handleSubmit에 의해 호출되며, 이벤트에 대한 파라미터를 받을 수 있습니다.

파라미터를 받을 수 있는 것은 각각의 컴포넌트에 적용된 register 함수와 연관이 있습니다. FormProvider로 래핑된 컴포넌트 내에서 register 함수를 등록하면 입력 요소가 자동으로 관리되고 양식 제출과 유효성 검사 과정이 가능해집니다.

(좌) 프로젝트 / (우) 네이버 계산기

 

이제 프로젝트를 실행하고 계산하기 버튼을 눌러보겠습니다.

onSubmit을 통해 전달받은 입력 요소들이 조회되는 것을 확인할 수 있습니다. 그리고 값이 비어있다면 onInvalid를 통해 alert 창이 출력됩니다.

(좌) 프로젝트 / (우) 네이버 계산기(좌) 프로젝트 / (우) 네이버 계산기

 

그리드 컴포넌트로 이동해보겠습니다. 마찬가지로 계산기 컴포넌트로부터 넘어온 params 값이 잘 조회됩니다.

(좌) 프로젝트 / (우) 네이버 계산기

 

계산식 적용하기

기획 단계에서 조사한 계산식을 실제로 적용해보겠습니다.

 

app/components/grid/grid.tsx

import { useEffect, useState } from "react"; import styles from "./grid.module.scss"; export type GridType = { paymentMethod: string; amount: string; period: string; interest: string; }; type ResultType = { round: number; principalPayment: string; interest: string; monthlyPayment: string; remainPayment: string; }; type Props = { params: GridType | undefined; }; const MyGrid = ({ params }: Props) => { const [result, setResult] = useState<ResultType[]>(); /** * 원리금 균등 분할 상환 * 계산식 : M = P * r * (1 + r)^n / ((1 + r)^n - 1) * @param amount * @param monthlyRate * @param month */ const formula1 = (amount: number, monthlyRate: number, month: number) => { const newResults = []; let remainPayment = amount; let index = 1; while (index <= month) { // 총 납부액 const monthlyPayment = (amount * monthlyRate * Math.pow(1 + monthlyRate, month)) / (Math.pow(1 + monthlyRate, month) - 1); // 이자액 : 잔여원금 * 월 이자율 const interest = remainPayment * monthlyRate; // 상환원금 : 총 납부액 - 이자액 const principalPayment = monthlyPayment - interest; // 잔여원금 : 잔여원금 - 상환원금 remainPayment = remainPayment - principalPayment; // console.log(`-- ${index}회차 --`); // console.log(`총납부액 : ${monthlyPayment)}`); // console.log(`이자액 : ${interest}`); // console.log(`상환원금 : ${principalPayment}`); // console.log(`잔여원금 : ${remainPayment}`); newResults.push({ round: index, principalPayment: Math.round(principalPayment).toLocaleString(), interest: Math.round(interest).toLocaleString(), monthlyPayment: Math.round(monthlyPayment).toLocaleString(), remainPayment: Math.abs(Math.round(remainPayment)).toLocaleString(), }); index++; } setResult(newResults); }; /** * 원금 균등 분할 상환 * 계산식 : M = P / n + (P * r) * @param amount * @param monthlyRate * @param month */ const formula2 = (amount: number, monthlyRate: number, month: number) => { const newResults = []; let remainPayment = amount; let index = 1; let principalPayment = 0; while (index <= month) { // 이자액 : 잔여원금 * 월 이자율 const interest = remainPayment * monthlyRate; // 총 납부액 const monthlyPayment = amount / month + interest; // 상환원금 : 총 납부액 - 이자액 if (index === 1) { principalPayment = monthlyPayment - interest; } // 잔여원금 : 잔여원금 - 상환원금 remainPayment = remainPayment - principalPayment; // console.log(`-- ${index}회차 --`); // console.log(`총납부액 : ${monthlyPayment}`); // console.log(`이자액 : ${interest}`); // console.log(`상환원금 : ${principalPayment}`); // console.log(`잔여원금 : ${remainPayment}`); newResults.push({ round: index, principalPayment: Math.round(principalPayment).toLocaleString(), interest: Math.round(interest).toLocaleString(), monthlyPayment: Math.round(monthlyPayment).toLocaleString(), remainPayment: Math.abs(Math.round(remainPayment)).toLocaleString(), }); index++; } setResult(newResults); }; /** * 만기일시 상환 * 계산식 : M = P * r * @param amount * @param monthlyRate * @param month */ const formula3 = (amount: number, monthlyRate: number, month: number) => { const newResults = []; let index = 1; let principalPayment = 0; while (index <= month) { // 이자액 : 잔여원금 * 월 이자율 const interest = amount * monthlyRate; // 상환원금 : 마지막 회차에 납부 if (index === month) { principalPayment = amount; } // 총 납부액 const monthlyPayment = interest + principalPayment; // 잔여원금 const remainPayment = amount - principalPayment; // console.log(`-- ${index}회차 --`); // console.log(`총납부액 : ${monthlyPayment}`); // console.log(`이자액 : ${interest}`); // console.log(`상환원금 : ${principalPayment}`); // console.log(`잔여원금 : ${remainPayment}`); newResults.push({ round: index, principalPayment: Math.round(principalPayment).toLocaleString(), interest: Math.round(interest).toLocaleString(), monthlyPayment: Math.round(monthlyPayment).toLocaleString(), remainPayment: Math.abs(Math.round(remainPayment)).toLocaleString(), }); index++; } setResult(newResults); }; useEffect(() => { const method = params?.paymentMethod; // 대출원금 const amount = Number(params?.amount); // 월 이자율 : 대출 연이율 / 12 / 100 const monthlyRate = Number(params!.interest) / 12 / 100; // 대출기간(월 변환) const month = Number(params?.period!) * 12; switch (method) { case "1": formula1(amount, monthlyRate, month); break; case "2": formula2(amount, monthlyRate, month); break; case "3": formula3(amount, monthlyRate, month); break; } }, []); return ( <div className={styles.grid_container}> <ul className={styles.grid_header}> <li style={{ width: "10%" }}>회차</li> <li style={{ width: "22%" }}>상환원금</li> <li style={{ width: "22%" }}>이자액</li> <li style={{ width: "23%" }}>총납부액</li> <li style={{ width: "23%" }}>잔여원금</li> </ul> {result?.map((obj, index) => { return ( <ul className={styles.grid_body} key={index}> <li style={{ width: "10%", textAlign: "center" }}>{obj.round}</li> <li style={{ width: "22%", textAlign: "right" }}>{obj.principalPayment}</li> <li style={{ width: "22%", textAlign: "right" }}>{obj.interest}</li> <li style={{ width: "23%", textAlign: "right" }}>{obj.monthlyPayment}</li> <li style={{ width: "23%", textAlign: "right" }}>{obj.remainPayment}</li> </ul> ); })} </div> ); }; export default MyGrid;

 

 

각각의 계산식에서 사용된 변수들의 의미는 다음과 같습니다. 

  • M : 매월 상환하는 금액
  • P : 대출 원금
  • r : 대출 연이율의 월 이자율
  • n : 대출 기간(월 단위)

 

r 값을 구하기 위해서는 대출 연이율을 12로 나누고 다시 100으로 나누면 됩니다.(이자율을 백분율로 변환)

n 값은 대출기간을 12로 곱하여 월로 변환합니다. 

각각의 계산식에서는 소수점 이하에서 반올림 처리(Math.round()) 했습니다. 소수점 계산에 의해 다른 계산기와 미미한 오차가 발생할 수 있습니다.

 

formula1()

원리금 균등 분할 상환 방식에 사용된 계산식입니다. n제곱은 javascript에서 Math.pow로 구현할 수 있습니다.

M = P * r * (1 + r)^n / ((1 + r)^n - 1)

 

formula2()

원금 균등 분할 상환 방식에 사용된 계산식은 다음과 같습니다.

원금 균등 방식은 상환 원금이 일정합니다. 이를 위해 index가 1일 때에만 총 납부액 - 이자액을 통해 상환 원금을 산출해내어 계산했습니다.

M = P / n + (P * r)

 

formula3()

만기일시 상환 방식에 사용된 계산식은 다음과 같습니다.

대출 기간동안 매월 이자를 상환하고 마지막 회차에 대출원금과 이자를 한번에 상환하는 방식으로 index와 month가 같아질 때 상환 원금이 계산되도록 했습니다.

M = P * r

 

계산된 결과는 setResult를 통해 배열로 담기고 화면에 그려지게 됩니다.

마지막으로 그리드 스타일을 적용해줍니다. 이후 모바일 환경을 고려하여 미디어쿼리를 적용했습니다.

 

app/components/grid/grid.module.scss

.grid_container { font-size: 15px; .contents { height: 80vh; overflow: auto; box-shadow: rgba(33, 35, 38, 0.1) 0px 10px 10px -10px; margin-bottom: 20px; @media (max-width: 480px) { height: 65vh; } } @media (max-width: 480px) { font-size: 13px; } .grid_header { min-height: 55px; display: flex; gap: 5px; padding: 0 10px; align-items: center; background: #f9fafb; margin-top: 20px; li { text-align: center; } } .grid_body { font-family: "BookkMyungjo-Bd"; display: flex; gap: 10px; align-items: center; padding: 0 10px; transition: background-color 0.2s ease; min-height: 45px; } }

 

 

네이버 계산기를 통해 결과값을 비교해보겠습니다.

 

조건1

  • 상환방식 : 원리금 균등
  • 대출원금 : 1억
  • 대출기간 : 1년
  • 연이자 : 4.5%

(좌) 프로젝트 / (우) 네이버 계산기(좌) 프로젝트 / (우) 네이버 계산기
(좌) 프로젝트 / (우) 네이버 계산기

 

 

조건2

  • 상환방식 : 원금 균등
  • 대출원금 : 1억
  • 대출기간 : 1년
  • 연이자 : 4.5%

(좌) 프로젝트 / (우) 네이버 계산기(좌) 프로젝트 / (우) 네이버 계산기
(좌) 프로젝트 / (우) 네이버 계산기

 

 

조건3

  • 상환방식 : 만기일시
  • 대출원금 : 1억
  • 대출기간 : 1년
  • 연이자 : 4.5%

(좌) 프로젝트 / (우) 네이버 계산기(좌) 프로젝트 / (우) 네이버 계산기
(좌) 프로젝트 / (우) 네이버 계산기

 

 

조건4

  • 상환방식 : 원리금 균등
  • 대출원금 : 2억
  • 대출기간 : 5년
  • 연이자 : 4.5%

(좌) 프로젝트 / (우) 네이버 계산기(좌) 프로젝트 / (우) 네이버 계산기

 

 

계산 결과가 잘 맞아떨어지는 것을 확인할 수 있습니다.

다음 편에서 이 계산 결과를 공유하는 기능을 만들어보겠습니다.

전체 코드는 GitHub에서 확인하실 수 있습니다.

 

참고문서
 

Portals – React

A JavaScript library for building user interfaces

ko.legacy.reactjs.org

반응형

포스팅 주소를 복사했습니다.

이 글이 도움이 되었다면 공감 부탁드립니다.