diff --git a/.gitignore b/.gitignore index 4d29575..f491785 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,6 @@ # dependencies /node_modules -/.pnp -.pnp.js # testing /coverage diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index e82e733..a41117e 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -1,5 +1,7 @@ ## 요구 사항 +### Step 1 + - [x] Class Component를 사용합니다. - [x] 레벨1을 참고하여 REQUIREMENTS.md에 요구 사항 도출 - [x] 2개의 숫자에 대해 덧셈이 가능하다. @@ -12,3 +14,9 @@ - [x] 출력값 있는 상황에 사용자의 페이지 이탈시 confirm을 활용해 사용자의 이탈 여부를 확인한다. - [x] 항상 사용자의 이탈시 마지막 출력값을 Local Storage에 저장한다. - [x] 연산의 결과값이 Infinity일 경우 오류라는 문자열을 보여준다. (아이폰 참고) + +### Step 2 + +- [x] Step1의 Class Component를 Function Component로 마이그레이션 합니다. +- [x] Step1에서의 피드백을 적용합니다. +- [x] 전체 코드를 리팩토링 합니다. diff --git a/public/index.html b/public/index.html index aa069f2..a11b18b 100644 --- a/public/index.html +++ b/public/index.html @@ -1,5 +1,5 @@ - + @@ -7,37 +7,14 @@ - - - React App + 리액트 계산기 - +
- diff --git a/public/robots.txt b/public/robots.txt index e9e57dc..eb05362 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -1,3 +1,2 @@ -# https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 52c91fb..0000000 --- a/src/App.js +++ /dev/null @@ -1,137 +0,0 @@ -import React, { Component } from "react"; -import DisplayResult from "./component/DisplayResult"; -import CalculatorButton from "./component/CalculatorButton"; - -export default class App extends Component { - state = { - firstNumber: 0, - operator: null, - secondNumber: 0, - isFirstNumber: true, - result: 0, - }; - - componentDidMount() { - const prevValue = localStorage.getItem("prevValue") || 0; - this.setFirstNumber(Number(prevValue)); - this.setResult(Number(prevValue)); - - window.addEventListener("beforeunload", function (e) { - e.preventDefault(); - e.returnValue = ""; - }); - } - - initState = () => { - this.setState({ - firstNumber: 0, - operator: null, - secondNumber: 0, - isFirstNumber: true, - result: 0, - }); - }; - - setFirstNumber = (number) => { - this.setState( - { - firstNumber: Number(number), - }, - localStorage.setItem("prevValue", number) - ); - }; - - setOperator = (operator) => { - this.setState({ operator }); - }; - - setSecondNumber = (number) => { - this.setState( - { - secondNumber: Number(number), - }, - localStorage.setItem("prevValue", number) - ); - }; - - setResult = (result) => { - this.setState({ result }); - }; - - setIsFirstNumber = (isFirstNumber) => { - this.setState({ isFirstNumber }); - }; - - calculate = () => { - const res = (() => { - switch (this.state.operator) { - case "+": - return this.add(); - case "-": - return this.sub(); - case "X": - return this.multiple(); - case "/": - return this.divide(); - default: - throw new Error("존재하지 않는 연산자입니다."); - } - })(); - - this.setState({ result: res }, () => { - this.initState(); - - if (res === Infinity || isNaN(res)) { - this.setFirstNumber(0); - this.setResult("오류"); - localStorage.setItem("prevValue", "오류"); - return; - } - - this.setFirstNumber(res); - this.setResult(res); - localStorage.setItem("prevValue", res); - }); - }; - - add() { - return this.state.firstNumber + this.state.secondNumber; - } - - sub() { - return this.state.firstNumber - this.state.secondNumber; - } - - divide() { - return Math.floor(this.state.firstNumber / this.state.secondNumber); - } - - multiple() { - return this.state.firstNumber * this.state.secondNumber; - } - - render() { - return ( - <> -
-
- - -
-
- - ); - } -} diff --git a/src/Calculator.js b/src/Calculator.js new file mode 100644 index 0000000..e55b5c7 --- /dev/null +++ b/src/Calculator.js @@ -0,0 +1,97 @@ +import React, { useEffect, useState, useCallback } from "react"; +import DisplayResult from "./component/DisplayResult"; +import NumberButton from "./component/buttons/NumberButton"; +import ModifierButton from "./component/buttons/ModifierButton"; +import OperatorButton from "./component/buttons/OperatorButton"; +import calculate from "./utils/calculate"; + +import "./css/calculator.css"; + +export default function App() { + const [firstNumber, setFirstNumber] = useState(0); + const [operator, setOperator] = useState(null); + const [secondNumber, setSecondNumber] = useState(0); + const [isFirstNumber, setIsFirstNumber] = useState(true); + const [result, setResult] = useState(0); + + useEffect(() => { + const prevValue = localStorage.getItem("prevValue") || result; + setFirstNumber(Number(prevValue)); + setResult(Number(prevValue)); + + window.addEventListener("beforeunload", function (e) { + e.preventDefault(); + e.returnValue = ""; + }); + }, []); + + const onClickNumber = (e) => { + const inputNumber = e.target.textContent; + const resultNumber = result === 0 ? inputNumber : result + inputNumber; + setResult(resultNumber); + + if (isFirstNumber) { + setFirstNumber(firstNumber * 10 + Number(inputNumber)); + return; + } + + setSecondNumber(secondNumber * 10 + Number(inputNumber)); + }; + + const onClickOperator = (e) => { + if (firstNumber === "") return; + + const inputOperator = e.target.textContent; + if (inputOperator === "=" && secondNumber === "") return; + + if (inputOperator !== "=") { + setResult(result + inputOperator); + setOperator(inputOperator); + setIsFirstNumber(false); + return; + } + + onCalculate(); + }; + + const onClickModifier = useCallback(() => { + setFirstNumber(0); + setSecondNumber(0); + setOperator(null); + setIsFirstNumber(true); + setResult(0); + localStorage.setItem("prevValue", 0); + }, []); + + const onCalculate = () => { + const res = calculate(firstNumber, secondNumber, operator); + + if (res === Infinity || isNaN(res)) { + setFirstNumber(0); + setSecondNumber(0); + setResult("오류"); + setOperator(null); + setIsFirstNumber(true); + localStorage.setItem("prevValue", "오류"); + return; + } + + setFirstNumber(res); + setSecondNumber(0); + setResult(res); + setOperator(null); + setIsFirstNumber(true); + localStorage.setItem("prevValue", res); + }; + + return ( +
+
+ + + + +
+
+ ); +} diff --git a/src/component/CalculatorButton.js b/src/component/CalculatorButton.js deleted file mode 100644 index c1eb7c5..0000000 --- a/src/component/CalculatorButton.js +++ /dev/null @@ -1,69 +0,0 @@ -import React, { Component } from "react"; - -export default class CalculatorButton extends Component { - onClickNumber = (e) => { - const inputNumber = e.target.textContent; - if (this.props.result === 0) { - this.props.setResult(inputNumber); - } else { - this.props.setResult(this.props.result + inputNumber); - } - - if (this.props.isFirstNumber) { - this.props.setFirstNumber( - this.props.firstNumber * 10 + Number(inputNumber) - ); - return; - } - this.props.setSecondNumber(this.props.secondNumber + inputNumber); - }; - - onClickOperator = (e) => { - const inputOperator = e.target.textContent; - if (this.props.firstNumber === "") return; - if (inputOperator === "=" && this.props.secondNumber === "") return; - this.props.setResult(this.props.result + inputOperator); - if (inputOperator !== "=") { - this.props.setFirstNumber(this.props.firstNumber); - this.props.setOperator(inputOperator); - this.props.setIsFirstNumber(false); - return; - } - - this.props.calculate(); - }; - - onClickModifier = () => { - this.props.initState(); - localStorage.setItem("prevValue", 0); - }; - - render() { - return ( - <> -
- - - - - - - - - - -
-
- -
-
- - - - - -
- - ); - } -} diff --git a/src/component/DisplayResult.js b/src/component/DisplayResult.js index f58beba..a91d82a 100644 --- a/src/component/DisplayResult.js +++ b/src/component/DisplayResult.js @@ -1,7 +1,5 @@ -import React, { Component } from "react"; +import React from "react"; -export default class DisplayResult extends Component { - render() { - return

{this.props.result.toString()}

; - } +export default function DisplayResult(props) { + return

{props.result.toString()}

; } diff --git a/src/component/buttons/ModifierButton.js b/src/component/buttons/ModifierButton.js new file mode 100644 index 0000000..a6177c1 --- /dev/null +++ b/src/component/buttons/ModifierButton.js @@ -0,0 +1,7 @@ +export default function ModifierButton(props) { + return ( +
+ +
+ ); +} diff --git a/src/component/buttons/NumberButton.js b/src/component/buttons/NumberButton.js new file mode 100644 index 0000000..52cea9a --- /dev/null +++ b/src/component/buttons/NumberButton.js @@ -0,0 +1,13 @@ +import { numberOfButtons, MaxButtonNumber } from "../../constants/constants"; + +export default function NumberButton(props) { + return ( +
+ {Array.from({ length: numberOfButtons }, (_, i) => ( + + ))} +
+ ); +} diff --git a/src/component/buttons/OperatorButton.js b/src/component/buttons/OperatorButton.js new file mode 100644 index 0000000..1881fb5 --- /dev/null +++ b/src/component/buttons/OperatorButton.js @@ -0,0 +1,13 @@ +import { operations } from "../../constants/constants"; + +export default function OperatorButton(props) { + return ( +
+ {Array.from(operations, (v, _) => ( + + ))} +
+ ); +} diff --git a/src/constants/constants.js b/src/constants/constants.js new file mode 100644 index 0000000..ea813a6 --- /dev/null +++ b/src/constants/constants.js @@ -0,0 +1,4 @@ +export const numberOfButtons = 10; +export const MaxButtonNumber = numberOfButtons - 1; +export const operations = ["/", "X", "-", "+", "="]; +export const ErrorMessage = "존재하지 않는 연산자입니다."; diff --git a/src/index.css b/src/css/calculator.css similarity index 86% rename from src/index.css rename to src/css/calculator.css index a8460c4..16df4d3 100644 --- a/src/index.css +++ b/src/css/calculator.css @@ -1,17 +1,3 @@ -html, -body { - margin: 0; - padding: 0; - font-family: sans-serif; -} - -#app { - height: 100vh; - display: flex; - justify-content: center; - align-items: center; -} - .calculator { width: 300px; display: grid; diff --git a/src/css/index.css b/src/css/index.css new file mode 100644 index 0000000..e41af14 --- /dev/null +++ b/src/css/index.css @@ -0,0 +1,13 @@ +html, +body { + margin: 0; + padding: 0; + font-family: sans-serif; +} + +#app { + height: 100vh; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/index.js b/src/index.js index 31508db..0329bf7 100644 --- a/src/index.js +++ b/src/index.js @@ -1,11 +1,11 @@ import React from "react"; import ReactDOM from "react-dom/client"; -import "./index.css"; -import App from "./App"; +import "./css/index.css"; +import Calculator from "./Calculator"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - + ); diff --git a/src/utils/calculate.js b/src/utils/calculate.js new file mode 100644 index 0000000..a4feea4 --- /dev/null +++ b/src/utils/calculate.js @@ -0,0 +1,36 @@ +import { ErrorMessage } from "../constants/constants"; + +const calculateValue = (firstNumber, secondNumber, operator) => { + return (() => { + switch (operator) { + case "+": + return add(firstNumber, secondNumber); + case "-": + return sub(firstNumber, secondNumber); + case "X": + return multiple(firstNumber, secondNumber); + case "/": + return divide(firstNumber, secondNumber); + default: + throw new Error(ErrorMessage); + } + })(); +}; + +const add = (firstNumber, secondNumber) => { + return firstNumber + secondNumber; +}; + +const sub = (firstNumber, secondNumber) => { + return firstNumber - secondNumber; +}; + +const divide = (firstNumber, secondNumber) => { + return Math.floor(firstNumber / secondNumber); +}; + +const multiple = (firstNumber, secondNumber) => { + return firstNumber * secondNumber; +}; + +export default calculateValue;