ReactNative Project - 계산기

bi_sz·2023년 10월 6일
0

ReactNative

목록 보기
14/37
post-thumbnail

MacOS의 기본 계산기 UI를 그대로 사용해서 계산기를 만들어보겠습니다.

로직 구현을 위해서 간단히 state 세팅을 하고, UI 개발을 먼저 진행한 후에 로직을 구현하고 예외처리를 진행하도록 하겠습니다.

계산기 위에 보이는 Test Text 들은 지워줬는데 영상은 다시 안 찍었어요. .ㅎ


프로젝트 생성

> npx create-expo-app calculator

calculator 프로젝트를 생성해주었습니다.


state 세팅

UI 개발을 하기 앞서서 계산기에서 필요한 state들을 먼저 세팅해주겠습니다.

화면을 구성할 때 화면에 바뀌는 값이 어떤건지 먼저 파악하는 것이 UI 개발에 중요한 단계입니다.

입력하는 값 inputusestate를 사용해주었고, 초기값은 0을 주었습니다.

숫자를 입력하고 연산자를 선택하게 됩니다.
선택한 연산자를 알려줄 currentOperatot, 초기값은 null로 주었습니다.

입력한 값이 저장되는 result, 초기값은 null 로 주었습니다.

= 연산자를 반복해서 눌렀을 때, 이전의 연산이 반복되게 해주기 위해
입력값을 저장해줄 tempInput, 초기값은 null을 주었습니다.

마찬가지로 연산자를 저장할 tempOperator, 초기값은 null 을 주었습니다.


UI 개발

Calculator

src 폴더를 생성해주었고, Calculator.js 파일을 추가해주었습니다.

계산기에서 사용되는 버튼을 총 3가지로 분류하였습니다.

초기화버튼, 연산자 버튼, 숫자 버튼 으로 버튼의 type 을 나누었습니다.

Button 입장에서 필요한 property 도 미리 정의해두었습니다.

  • text : 버튼 안에 들어갈 text ex) 1, 2, 3, x , =
  • onPress : 클릭했을 때의 동작
  • flex : 컨테이너 안에서 버튼의 크기제어
  • type : 버튼의 타입

버튼의 타입에 따라 색상을 설정해주었습니다.

미리 만들어 둔 버튼을 사용해서 초기화 버튼과 나누기 버튼을 만들어주었습니다.

버튼의 함수에 터치를 위해 TouchableOpacity 를 사용해주었고, onPess 를 널어주고 Text 에는 text를 넣어주었습니다.

스타일링도 수정해주었습니다.

App.js 에서 Calculator 를 추가해주고 실행해봅시다!

상단바 위쪽에 AC/ 가 그려지는 모습입니다.

SafeArea 를 이용하여 안전한 영역에서 그려지도록 해주었고,

<StatusBar style="auto" />

불필요한 코드를 삭제해주었습니다.

상단바 영역 아래로 그려지는 모습입니다.

버튼의 배경색을 설정해주었습니다.
버튼의 type마다 색상이 다르게 설정하는 부분을 추가하기에 코드가 길어질 수 있으므로, return문 위에 backgroundColor를 정의해주었습니다.

COLOR를 사용하려면 해당 선언부가 위쪽에 있어야하기 때문에 위로 옮겨주었습니다.

  • paddingVertical : topbottom을 늘려줍니다.
  • height : 높이를 고정시켜줍니다.

동일한 height 값으로 세팅해주는것이 좋을 것 같아 height를 사용하였습니다.

계산기의 첫 번째 버튼 줄 UI가 완성되었습니다.

export default () => {
  return (
    <View style={{ flex: 1 }}>
      {/* 결과 */}

      {/* [AC ~ /] */}
      <View style={{ flexDirection: "row", width: "100%" }}>
        <Button 
            type="reset"
            text="AC"
            onPress={() => null}
            flex={3}
        />
        <Button 
            type="operator"
            text="/"
            onPress={() => null}
            flex={1}
        />
      </View>

      {/* [7 ~ x] */}
      <View style={{ flexDirection: "row", width: "100%" }}>
        <Button 
            type="num"
            text="7"
            onPress={() => null}
            flex={1}
        />
        <Button 
            type="num"
            text="8"
            onPress={() => null}
            flex={1}
        />
        <Button 
            type="num"
            text="9"
            onPress={() => null}
            flex={1}
        />
        <Button 
            type="operator"
            text="X"
            onPress={() => null}
            flex={1}
        />
      </View>

      {/* [4 ~ -] */}
      <View style={{ flexDirection: "row", width: "100%" }}>
        <Button 
            type="num"
            text="4"
            onPress={() => null}
            flex={1}
        />
        <Button 
            type="num"
            text="5"
            onPress={() => null}
            flex={1}
        />
        <Button 
            type="num"
            text="6"
            onPress={() => null}
            flex={1}
        />
        <Button 
            type="operator"
            text="-"
            onPress={() => null}
            flex={1}
        />
      </View>

      {/* [1 ~ +] */}
      <View style={{ flexDirection: "row", width: "100%" }}>
        <Button 
            type="num"
            text="1"
            onPress={() => null}
            flex={1}
        />
        <Button 
            type="num"
            text="2"
            onPress={() => null}
            flex={1}
        />
        <Button 
            type="num"
            text="3"
            onPress={() => null}
            flex={1}
        />
        <Button 
            type="operator"
            text="+"
            onPress={() => null}
            flex={1}
        />
      </View>

      {/* [0 ~ =] */}
      <View style={{ flexDirection: "row", width: "100%" }}>
        <Button 
            type="num"
            text="0"
            onPress={() => null}
            flex={3}
        />
        <Button 
            type="operator"
            text="="
            onPress={() => null}
            flex={1}
        />
      </View>
    </View>
  )

나머지 버튼 부분도 추가해주었습니다.

계산기의 형태가 조금은 보입니다. ㅎ.ㅎ

스타일링을 추가해주었습니다.
구분선을 추가하였고, 계산기의 width 값을 250으로 고정해주었습니다.

스타일링이 추가된 모습입니다.


refactoring

버튼 부분의 코드를 보면 같은 형식의 코드가 반복적으로 나열되어 있습니다.

styled-component를 사용해보겠습니다.

>yarn add styled-component

명령어를 통해 styled-component 라이브러리를 추가해줍니다.

ButtonContainer를 정의해주었고, styledView, 그리고 스타일링을 설정해주었고, 해당 스타일링에 해당하는 VIEW 들을 ButtonContainer로 수정해주었습니다.

UI에는 변화가 없어야하지만, 상단바 부분을 무시하고 표시되는 모습입니다.

  • justifyContent : center를 주어 화면 중앙에 오도록 수정하였습니다.

기존 App.js에 있던 useState 부분을 Calculator.js 로 옮겨주었습니다.

InputContainer를 정의해주고 스타일링하여, 결과 부분에서 사용하였습니다.

padding: 5px; // top, right, bottom, left
padding: 10px 5px; // top,bottom(vertical), left,right(horizontal)
padding: 1px 2px 3px 4px; // top right botoom left
*padding**은 위 3가지 방식으로 줄 수 있습니다.

입력 칸이 생긴 모습입니다.


숫자 버튼 부분의 코드를 보면 text 를 제외한 부분은 동일합니다.

숫자를 배열로 두고, map함수를 돌립니다.
첫 번째 인자로는 각 배열의 원소가 오므로 num이 오게되고, Button컴포넌트를 그대로 return 해줍니다.

TextString 타입이여야 하는데 nummNumber 타입이기 때문에 [ ` ] 빽틱으로 String화 시켜줍니다.

사진에는 보이지 않지만 1,2,3 부분도 마찬가지로 수정해주었습니다.


logic

육안으로 보면서 개발이 가능하도록 각 state의 상황을 알 수 있는 Test Text 를 추가해주었습니다.

Number Input

숫자를 클릭했을 때 input값이 변경되도록 onPressNum 함수를 구현해주었습니다.

숫자 버튼부분에서 null 로 남겨두었던 부분에 onPressNum(num)으로 수정해주었습니다.

클릭한 숫자의 값으로 input값이 변경되는 것을 알 수 있습니다.

입력한 값은 Number 타입이기 때문에, 값을 더해주게 되면 1,2 를 입력했을 경우에 12가 되어야 하지만, 3 이 되어버립니다.

Number 타입의 값을 String 타입으로 변환하여 이어붙여줍니다.

그리고 최종으로 Number로 변환시켜 주었습니다.

NUmber 타입으로 변환시키지 않을 경우 012 와 같이 앞에 초기값0 이 붙기때문에 Number 타입으로 변환시켜 앞에 0이 붙지 않도록 해주었습니다.

잘 이어붙어지는 모습입니다.

Operator Function

연산자를 클릭했을 때의 함수를 만들어주었습니다.

operator인자를 currentOperator로 세팅해주었습니다.

다섯가지의 연산자 부분에 수정해주었고, 곱하기 부분을 X 에서 * 로 수정해주었습니다.

Test Text로 작성해둔 currentOperator 부분에 값이 잘 전달되는 것을 확인할 수 있습니다.

선택된 연산자에 Bold 효과를 주기 위해서 isSelected를 추가해주었습니다.

각 연산자에 currentOperator 부분을 추가해주었습니다.

선택한 연산자 부분에 Bold처리를 확인할 수 있습니다.
하단에 경고도 보입니다!

각각의 childunipueprops이 있어야 한다. 라는 경고입니다.

map 함수는 각각의 컴포넌트를 반환할 때 고유한 key값이 있어야합니다.

map함수를 사용한 부분에 key 값을 추가해주었습니다.


기존에는 클릭한 값을 input에 세팅하였는데,
선택한 연산자가 없을 때 newInput에 값을 세팅하고,
선택한 연산자가 있을 경우에는 input 값을 result에 세팅하고,
num값을 Input에 세팅해주었습니다.

연산자 부분도 클릭한 operator 버튼이 =이 아닐 경우에는 currentOperator에 연산자를 세팅하고, =일 경우에는 currentOperator에 맞는 연산을 하도록 수정해주었습니다.

그리고 최종적으로 finalResult의 값을 Result와, Input에 세팅해주었습니다.

초기화해줄 onPressReset을 추가해주었고 초기화 버튼에 추가해주었습니다.

연산자를 하나씩 실행해 보았습니다. 문제 없이 동작하는 모습입니다.

오류 수정

연산자를 입력한 후 두번 째 입력 값을 클릭할 때 2자리 이상 입력하지 못하는 오류가 있었습니다.

연산자가 입력된 상태면 동작하는 함수에서 오류가 발생하였습니다.

바로 직전에 클릭한 경우에만 동작하도록 조건을 추가해주었고, isClickedOperatoruseState 로 작성해주었습니다.

클릭한 연산자가 =이 아닐 때는 isClickedOperator 값을 true로 하고, 바로 직전에 연산자를 입력했을 때 input을 setResult 하면서 isClickedOperator의 값도 false로 바뀌도록 수정해주었습니다.

두 번째로 입력하는 값이 두 자리 이상이어도 잘 동작하는 모습입니다.


예외처리

바로 직전에 =을 클릭하였을 경우 마지막 입력 값과, 입력한 연산자를 tempInput, tempOperator 에 저장하여 =을 계속 클릭할 경우에 저장된 입력값과 연산자를 이용해 반복 계산을 할 수 있도록 하였습니다.

isClickedEqualuseState로 작성해주었고, 바로 직전에 =를 클릭 했으면, input 값 대신에 tempInput, currentOperator 연산자 대신에 tempOperator 연산자로 계산이 되도록 했습니다.

= 연산자를 계속 클릭할 경우 이전 입력값과 연산자를 기억하여 반복 연산하는 모습입니다.


계산기에서 input 값이 있을 때는 C, 아닐 때는 AC 로 구분됩니다.

어떠한 값을 boalean 값으로 변환시키기 위해서는 !!만 붙여주면 됩니다.

input이 있는지 없는지를 확인할 hasInput을 선언해주었습니다.

초기화 버튼의 TexthasInput 값에 따라 C, AC 가 보이게 수정해주었습니다.

초기화 로직도 수정해주었습니다.

hasInput 값이 있을 경우 input 값만 초기화해 주고, 없을 경우에는 AllClear 해줍니다.


Custom hook 으로 만들기

src폴더에 use-calculator.js 파일을 생성해주었습니다.

Calculator.js에서 사용해야하기 때문에 export 로 만들어줍니다.
hook은 모두 use라는 이름으로 시작해야합니다.

계산과 관련된 로직을 use-calculator.js로 옮겨주었습니다.

Calculator.js에서는 필요한 값만 리턴해주었습니다.

state, 변수함수를 output에서 구조분해 해줍니다.


Calculator.js에서는 UI만을 담당하고,
use-calculator.js에서는 로직을 담당합니다.

구분이 되어있기 때문에 유지보수에도 용이하고, 특정 부분에서 오류가 났을 경우 조치하기도 편리합니다.

profile
https://li-yo.tistory.com/ 티스토리 블로그 이전 하였습니다.

0개의 댓글