본문 바로가기
Programming/Vue

[실습] 뷰(Vue)로 계산기 구현하기

by 돌방로그 2022. 12. 14.

제목의 글을 설명하기에 앞서 정보전달의 목적도 있지만, 

제가 잊지않기 위해서 공부 및 정리하며 쓰는 글이라는 사실을 미리 고지합니다.

 

혹시라도 오입력된 정보가 있다면, 댓글 남겨주세요!


오늘의 Vue.js 실습 목표는 "계산기 만들어보기!" 입니다.

계산기 기능을 구현하기 위해서는 뷰 인스턴스 상태(Vue Instance State) 개념을 필수적으로 공부하고 구현해야 합니다.본 게시글에서 다루는 사항은 계산기를 구현하는 과정입니다.

 


계산기 구현하기

본 실습을 진행하면 기본적인 HTML과 CSS, 나아가 뷰의 인스턴스 상태(Vue Instance State) 중에서도 기본적인 data, methods에 대해 공부해볼 수 있습니다.

 

사전 준비

아래 사항에 대해서 사전 준비가 완료되지 않으신 분들은 아래 링크를 참조하여 사전 준비를 진행합니다.

  1. Vue 프로젝트 생성 (참고: https://logs-jejustone.tistory.com/5)

 

혹시라도 아래 개념이 잘 기억나지 않으시는 분들은 관련 링크를 참조하시기 바랍니다.

 

설계/기획

다양한 계산기 종류가 있으나 저의 경우 가계부를 작성할 때 계산기를 많이 이용합니다.

그래서 주로 이용하는 계산기의 동작 방식을 착안하여 개발할 계산기를 기획하였습니다.

Layout

기능

  • Clear 기능은 'Backspace(←)', 'Clear Entry(CE)', 'Clear All(AC)'로 총 3가지를 제공합니다.
    • ←: Backspace 기능
      • 입력 중인 필드에서 가장 마지막에 입력한 글자 1개를 삭제합니다.
      • 지울 글자가 없는 경우 입력 중인 필드에는 '0'으로 표시합니다.
    • CE: Clear Entry의 약자로 입력 중인 필드의 값 전체를 삭제합니다.
    • AC: Clear All의 약자로 입력 중인 필드와 입력 결과가 표시되는 필드 값 전체를 삭제합니다.
  • 연산 결과는 '=' 버튼이 클릭된 경우에만 진행합니다.
  • 숫자 버튼은 0~9까지 총 10개와 더불어 돈과 관련된 입력이 편리하도록 000으로 총 11개를 제공합니다.
    • 0, 000: 입력 중인 필드에서 1~9까지의 숫자가 입력되지 않은 경우, 버튼을 클릭해도 '0'으로 표시합니다.
  • 연산 기호는 곱셈(*), 나눗셈(/), 뺄셈(-), 덧셈(+)으로 총 4가지를 제공합니다.
  • 소숫점 기호로 '.'을 제공하며, 입력 중인 필드의 마지막 숫자에 대해서 소숫점을 적용합니다. 

 

결과 이미지

구현하기에 앞서 본 글을 따라하시면 아래와 같은 계산기를 확인하실 수 있습니다.

 

소스 코드

src\views\Practice\PracticeCalculator.vue

<template>

template 영역은 크게 입력 필드와 입력된 결과가 표시되는 필드로 2가지 영역으로 구분되었습니다.

입력된 결과가 표시되는 필드는 expgrp로 명명하고 영역을 분리할 수 있도록 <div> 태그로 선언하였습니다. 

<div id="expgrp">
    <div id="numexp_result">
        {{ strResult }}
    </div>
    <div id="numexp_input">
        {{ strInput }}
    </div>
</div>

입력 결과가 표시되는 필드는 2개 세부 영역으로 구분되며, 두 개 영역은 결과 표시 필드와 입력 중인 필드입니다.

 

<div id="btngrp">
    <div class="btnrow">
        <button v-on:click="clickBackspaceBtn">←</button>
        <button v-on:click="clickClearBtn('CE')">CE</button>
        <button v-on:click="clickClearBtn('AC')">AC</button>
        <button v-on:click="clickCalculateBtn('*')">X</button>
    </div>
    <div class="btnrow">
        <button v-on:click="clickNumberBtn('7')">7</button>
        <button v-on:click="clickNumberBtn('8')">8</button>
        <button v-on:click="clickNumberBtn('9')">9</button>
        <button v-on:click="clickCalculateBtn('/')">/</button>
    </div>
    <div class="btnrow">
        <button v-on:click="clickNumberBtn('4')">4</button>
        <button v-on:click="clickNumberBtn('5')">5</button>
        <button v-on:click="clickNumberBtn('6')">6</button>
        <button v-on:click="clickCalculateBtn('-')">-</button>
    </div>
    <div class="btnrow">
        <button v-on:click="clickNumberBtn('1')">1</button>
        <button v-on:click="clickNumberBtn('2')">2</button>
        <button v-on:click="clickNumberBtn('3')">3</button>
        <button v-on:click="clickCalculateBtn('+')">+</button>
    </div>
    <div class="btnrow">
        <button v-on:click="clickNumberBtn('000')">000</button>
        <button v-on:click="clickNumberBtn('0')">0</button>
        <button v-on:click="clickCalculateBtn('.')">.</button>
        <button v-on:click="clickCalculateBtn('=')">=</button>
    </div>
</div>

입력 필드는 btngrp로 명명하고 영역을 분리할 수 있도록 역시나 <div> 태그로 선언하였습니다.

세부적으로 총 4개 행으로 영역이 구분되어 있으며 한 행에 4개의 버튼이 표시되도록 구성하였습니다.

각 버튼은 'v-on:click'라는 뷰 이벤트 디렉티브(Vue Event Directive)로 버튼 클릭시 호출될 이벤트 함수를 정의하였습니다.

총 20개의 버튼은 20개의 함수를 호출하는 것이 아니라는 점이 핵심입니다.

 

아래는 template 영역의 전체 코드입니다. 

더보기
<template>
    <div id="wrapCalculator">
        <div id="calgrp">
            <div id="expgrp">
                <div id="numexp_result">
                    {{ strResult }}
                </div>
                <div id="numexp_input">
                    {{ strInput }}
                </div>
            </div>
            <div id="btngrp">
                <div class="btnrow">
                    <button v-on:click="clickBackspaceBtn">←</button>
                    <button v-on:click="clickClearBtn('CE')">CE</button>
                    <button v-on:click="clickClearBtn('AC')">AC</button>
                    <button v-on:click="clickCalculateBtn('*')">X</button>
                </div>
                <div class="btnrow">
                    <button v-on:click="clickNumberBtn('7')">7</button>
                    <button v-on:click="clickNumberBtn('8')">8</button>
                    <button v-on:click="clickNumberBtn('9')">9</button>
                    <button v-on:click="clickCalculateBtn('/')">/</button>
                </div>
                <div class="btnrow">
                    <button v-on:click="clickNumberBtn('4')">4</button>
                    <button v-on:click="clickNumberBtn('5')">5</button>
                    <button v-on:click="clickNumberBtn('6')">6</button>
                    <button v-on:click="clickCalculateBtn('-')">-</button>
                </div>
                <div class="btnrow">
                    <button v-on:click="clickNumberBtn('1')">1</button>
                    <button v-on:click="clickNumberBtn('2')">2</button>
                    <button v-on:click="clickNumberBtn('3')">3</button>
                    <button v-on:click="clickCalculateBtn('+')">+</button>
                </div>
                <div class="btnrow">
                    <button v-on:click="clickNumberBtn('000')">000</button>
                    <button v-on:click="clickNumberBtn('0')">0</button>
                    <button v-on:click="clickCalculateBtn('.')">.</button>
                    <button v-on:click="clickCalculateBtn('=')">=</button>
                </div>
            </div>
        </div>
    </div>
</template>

 

<script> 영역

script 영역은 계산기에서 사용할 변수 및 함수를 정의하는 영역입니다.

다양한 뷰 인스턴스 상태(Vue Instance State) 중에서도 data와 methods를 사용하였습니다.

 

data 영역에서는 입력된 결과가 표시되는 필드에서 사용할 3가지 변수가 선언되어 있습니다.

data: function() {
    return {
        strResult: '0',
        strInput: '0',
        strLastInput: '0',
    };
},
  • strResult: 연산 결과에 해당하는 수식을 지니기 위한 변수
  • strInput: 연산 결과를 입력하는 수식을 지니기 위한 변수
  • strLastInput: 연산 결과를 입력하는 수식의 마지막 입력 글자를 지니는 변수

 

method 영역에서는 <template>에서 'v-on:click'로 호출될 함수가 정의된 영역으로 data 영역에서 선언된 변수를 이용합니다.

methods: {
    printLog: function (btnName) {
        console.log('Press the ' + btnName + ' button');
    },

    // Calculator Button
    clickNumberBtn: function (strNum) {
        this.printLog(strNum);
        this.strLastInput = strNum;

        if (this.strInput == '0') {
            if (strNum != '000' &&
                strNum != '0') {
                this.strInput = strNum;
            }
        }
        else {
            this.strInput += strNum;
        }
    },
    clickCalculateBtn: function (strSymbol) {
        this.printLog(strSymbol);
        if (strSymbol == '=') {
            // 연산 수식
            this.strResult = this.strInput + ' ' + strSymbol;

            // 연산 수행 및 결과
            const elements = this.strResult.split(' ');
            let numResult = parseFloat(elements[0]);
            for (let i = 1; i < elements.length; i += 2) {
                console.log('numResult:' + numResult);
                console.log('elements[i]: ' + elements[i]);
                console.log('elements[i+1]: ' + elements[i + 1]);

                if (elements[i] == '*') {
                    numResult *= parseFloat(elements[i + 1]);
                }
                else if (elements[i] == '/') {
                    numResult /= parseFloat(elements[i + 1]);
                }
                else if (elements[i] == '-') {
                    numResult -= parseFloat(elements[i + 1]);
                }
                else if (elements[i] == '+') {
                    numResult += parseFloat(elements[i + 1]);
                }
            }
            console.log('numResult:' + numResult);
            this.strInput = numResult.toString();
            this.strLastInput = this.strInput;
        }
        else if (strSymbol == '.') {
            this.strInput += strSymbol;
        }
        else if (this.strLastInput != '*' &&
            this.strLastInput != '/' &&
            this.strLastInput != '-' &&
            this.strLastInput != '+') {
            this.strLastInput = strSymbol;
            this.strInput += ' ' + strSymbol + ' ';
        }
        else {
            alert('Press number button!!!');
        }
    },

    // Clear Button
    clickBackspaceBtn: function () {
        this.printLog('Backspace Button');
        this.strInput = this.strInput.substring(0, this.strInput.length - 1);

        if (this.strInput.length == 0) {
            this.strInput = '0';
        }
    },
    clickClearBtn: function(strSymbol) {
        this.printLog('Clear ' + strSymbol + ' Button');
        this.strInput = '0';

        if (strSymbol == 'AC') {
            this.strResult = '0';
            this.strLastInput = '0';
        }
    }
}

계산기의 20개 버튼에 대해서 총 5개의 함수를 구현하였습니다. 5개의 함수를 구현할 때는 위 설계/기획 단계에서 언급한 예외 조건들 놓치지 않고 분기처리를 하여 구현합니다.

  • printLog: F12(개발자모드)의 콘솔 영역에서 표시될 메세지를 구현하는 함수
  • clickNumberBtn: 숫자 버튼과 관련된 로직을 구현하는 함수
  • clickCalculateBtn: 연산 기호 버튼과 관련된 로직을 구현하는 함수
  • clickBackspaceBtn: 입력 중인 필드에서 마지막으로 입력한 한 글자를 삭제하는 로직을 구현하는 함수
  • clickClearBtn: 입력된 결과가 표시되는 필드의 AC와 CE 버튼에 대한 로직을 구현하는 함수

 

아래는 script 영역의 전체 코드입니다. 

더보기
<script>
export default {
    data: function() {
        return {
            strResult: '0',
            strInput: '0',
            strLastInput: '0',
        };
    },
    methods: {
        printLog: function (btnName) {
            console.log('Press the ' + btnName + ' button');
        },

        // Calculator Button
        clickNumberBtn: function (strNum) {
            this.printLog(strNum);
            this.strLastInput = strNum;

            if (this.strInput == '0') {
                if (strNum != '000' &&
                    strNum != '0') {
                    this.strInput = strNum;
                }
            }
            else {
                this.strInput += strNum;
            }
        },
        clickCalculateBtn: function (strSymbol) {
            this.printLog(strSymbol);
            if (strSymbol == '=') {
                // 연산 수식
                this.strResult = this.strInput + ' ' + strSymbol;

                // 연산 수행 및 결과
                const elements = this.strResult.split(' ');
                let numResult = parseFloat(elements[0]);
                for (let i = 1; i < elements.length; i += 2) {
                    console.log('numResult:' + numResult);
                    console.log('elements[i]: ' + elements[i]);
                    console.log('elements[i+1]: ' + elements[i + 1]);

                    if (elements[i] == '*') {
                        numResult *= parseFloat(elements[i + 1]);
                    }
                    else if (elements[i] == '/') {
                        numResult /= parseFloat(elements[i + 1]);
                    }
                    else if (elements[i] == '-') {
                        numResult -= parseFloat(elements[i + 1]);
                    }
                    else if (elements[i] == '+') {
                        numResult += parseFloat(elements[i + 1]);
                    }
                }
                console.log('numResult:' + numResult);
                this.strInput = numResult.toString();
                this.strLastInput = this.strInput;
            }
            else if (strSymbol == '.') {
                this.strInput += strSymbol;
            }
            else if (this.strLastInput != '*' &&
                this.strLastInput != '/' &&
                this.strLastInput != '-' &&
                this.strLastInput != '+') {
                this.strLastInput = strSymbol;
                this.strInput += ' ' + strSymbol + ' ';
            }
            else {
                alert('Press number button!!!');
            }
        },

        // Clear Button
        clickBackspaceBtn: function () {
            this.printLog('Backspace Button');
            this.strInput = this.strInput.substring(0, this.strInput.length - 1);

            if (this.strInput.length == 0) {
                this.strInput = '0';
            }
        },
        clickClearBtn: function(strSymbol) {
            this.printLog('Clear ' + strSymbol + ' Button');
            this.strInput = '0';

            if (strSymbol == 'AC') {
                this.strResult = '0';
                this.strLastInput = '0';
            }
        }
    }
}
</script>

 

<style> 영역

style 영역은 template 영역에서 정의한 Layout에 CSS로 스타일 효과를 주는 영역입니다.

template 영역에서 사용한 id와 class에 대해서 기획/설계에서 정의한 이미지를 참고하여 구현하였습니다.

CSS는 자유롭게 꾸밀 수 있기 때문에 별다른 설명없이 코드만 첨부합니다.

<style scoped>
#wrapCalculator {
    padding: 5px;
    width: 260px;
}

#wrapCalculator h2 {
    padding: 5px;
    text-align: center;

    background-color: white;
}

#calgrp {
    padding: 5px;
    background-color: white;
    border: 2px dotted black;
}

/* STYLE: Expression group of the Calculator */
#expgrp {
    padding: 5px;
    background-color: lightcoral;
    text-align: right;
    color: white;
}

#numexp_result {
    margin: 5px 5px 0px 5px;

    font-size: x-small;
}

#numexp_input {
    margin: 0px 5px 0px 5px;
    font-size: x-large;
}

/* STYLE: Button group of the calculator */
.btnrow {
    margin: 5px;

    border: 2px solid white;
}

#btngrp button {
    margin: 5px;
    padding: 10px;

    background-color: lightcoral;
    border-color: white;

    text-align: center;
    font-size: medium;
    width: 45px;
    height: 45px;
}
</style>

 

 


References

 

HTML Tutorial

www.w3schools.com

 

CSS Tutorial

www.w3schools.com

 

Quick Start | Vue.js

vuejs.org

 

 

댓글