본문 바로가기

카테고리 없음

[React] React로 공학용 계산기 만들기

반응형

 

안녕하세요! 오늘은 React와 shadcn/ui를 사용하여 완전한 기능을 갖춘 공학용 계산기를 만들어보겠습니다. 이 튜토리얼을 통해 React의 상태 관리, 이벤트 처리, 그리고 UI 컴포넌트 구성 방법을 배울 수 있습니다.

프로젝트 설정

먼저 필요한 의존성을 설치합니다:

# shadcn/ui 컴포넌트 설치
npx create-next-app@latest my-calculator --typescript --tailwind --eslint
cd my-calculator
npx shadcn-ui@latest init

컴포넌트 구조

우리의 계산기는 단일 React 컴포넌트로 구현됩니다. 주요 기능은 다음과 같습니다:

  • 기본 산술 연산 (덧셈, 뺄셈, 곱셈, 나눗셈)
  • 공학 계산 기능 (삼각함수, 로그, 제곱근)
  • 상수 값 (π, e)
  • 메모리 기능

상태 관리

계산기에는 4개의 주요 상태가 필요합니다:

const [display, setDisplay] = useState('0');          // 현재 화면에 표시되는 값
const [memory, setMemory] = useState(null);           // 이전 계산 값 저장
const [waitingForOperand, setWaitingForOperand] = useState(true);  // 새로운 숫자 입력 대기 상태
const [pendingOperator, setPendingOperator] = useState(null);      // 대기 중인 연산자

계산 로직 구현

1. 숫자 입력 처리

const inputDigit = (digit) => {
  if (waitingForOperand) {
    setDisplay(String(digit));
    setWaitingForOperand(false);
  } else {
    setDisplay(display === '0' ? String(digit) : display + digit);
  }
};

2. 기본 연산 처리

const performOperation = (nextOperator) => {
  const nextValue = parseFloat(display);

  const operations = {
    '+': (prevValue, nextValue) => prevValue + nextValue,
    '-': (prevValue, nextValue) => prevValue - nextValue,
    '×': (prevValue, nextValue) => prevValue * nextValue,
    '÷': (prevValue, nextValue) => prevValue / nextValue,
  };

  if (memory === null) {
    setMemory(nextValue);
  } else if (pendingOperator) {
    const currentValue = memory || 0;
    const computedValue = operations[pendingOperator](currentValue, nextValue);

    setMemory(computedValue);
    setDisplay(String(computedValue));
  }

  setWaitingForOperand(true);
  setPendingOperator(nextOperator);
};

3. 특수 함수 처리

const performSpecialOperation = (operation) => {
  const currentValue = parseFloat(display);
  let result;

  switch (operation) {
    case 'sin':
      result = Math.sin(currentValue * Math.PI / 180);
      break;
    case 'cos':
      result = Math.cos(currentValue * Math.PI / 180);
      break;
    case 'tan':
      result = Math.tan(currentValue * Math.PI / 180);
      break;
    // ... 기타 특수 함수들
  }

  setDisplay(String(result));
  setWaitingForOperand(true);
};

UI 구현

UI는 shadcn/ui의 Card와 Button 컴포넌트를 사용하여 구현합니다. 계산기는 그리드 레이아웃을 사용하여 버튼들을 배치합니다.

return (
  <Card className="w-96">
    <CardContent className="p-4">
      {/* 디스플레이 */}
      <div className="mb-4">
        <input
          type="text"
          value={display}
          readOnly
          className="w-full text-right p-2 text-2xl bg-gray-100 rounded"
        />
      </div>

      {/* 버튼 그리드 */}
      <div className="grid grid-cols-4 gap-2">
        {/* 과학 기능 버튼 */}
        <Button onClick={() => performSpecialOperation('sin')} variant="outline">sin</Button>
        <Button onClick={() => performSpecialOperation('cos')} variant="outline">cos</Button>
        {/* ... 기타 버튼들 */}
      </div>
    </CardContent>
  </Card>
);

주요 기능 설명

  1. 숫자 입력
    • 사용자가 숫자 버튼을 클릭하면 inputDigit 함수가 호출됩니다.
    • 현재 상태가 새로운 숫자 입력을 기다리는 중이라면 디스플레이를 새 숫자로 교체합니다.
    • 그렇지 않다면 현재 디스플레이에 새 숫자를 추가합니다.
  2. 연산자 처리
    • 연산자 버튼을 클릭하면 performOperation 함수가 호출됩니다.
    • 이전 계산 결과가 있다면 현재 입력된 숫자와 연산을 수행합니다.
    • 결과를 메모리에 저장하고 디스플레이를 업데이트합니다.
  3. 특수 함수
    • sin, cos, tan 등의 특수 함수는 performSpecialOperation 함수에서 처리됩니다.
    • 현재 디스플레이 값을 입력으로 사용하여 계산을 수행합니다.
    • 각도는 도(degree) 단위로 입력받아 라디안으로 변환하여 계산합니다.

사용자 경험 개선

  1. 시각적 피드백
    • 버튼 클릭 시 시각적 피드백을 제공하기 위해 shadcn/ui의 버튼 스타일을 활용
    • 연산자 버튼은 다른 색상을 사용하여 구분
  2. 에러 처리
    • 0으로 나누기 등의 에러 상황 처리
    • 잘못된 입력에 대한 피드백 제공

확장 가능성

이 계산기는 다음과 같은 방향으로 확장할 수 있습니다:

  1. 추가 공학 함수 구현 (역삼각함수, 지수함수 등)
  2. 단위 변환 기능
  3. 기록 기능 추가
  4. 테마 지원 (다크 모드/라이트 모드)

전체 소스 코드

전체 소스 코드는 아래와 같습니다:

import React, { useState } from 'react';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';

const ScientificCalculator = () => {
  const [display, setDisplay] = useState('0');
  const [memory, setMemory] = useState(null);
  const [waitingForOperand, setWaitingForOperand] = useState(true);
  const [pendingOperator, setPendingOperator] = useState(null);

  const clearAll = () => {
    setDisplay('0');
    setMemory(null);
    setWaitingForOperand(true);
    setPendingOperator(null);
  };

  const inputDigit = (digit) => {
    if (waitingForOperand) {
      setDisplay(String(digit));
      setWaitingForOperand(false);
    } else {
      setDisplay(display === '0' ? String(digit) : display + digit);
    }
  };

  const inputDecimal = () => {
    if (waitingForOperand) {
      setDisplay('0.');
      setWaitingForOperand(false);
    } else if (display.indexOf('.') === -1) {
      setDisplay(display + '.');
    }
  };

  const performOperation = (nextOperator) => {
    const nextValue = parseFloat(display);

    const operations = {
      '+': (prevValue, nextValue) => prevValue + nextValue,
      '-': (prevValue, nextValue) => prevValue - nextValue,
      '×': (prevValue, nextValue) => prevValue * nextValue,
      '÷': (prevValue, nextValue) => prevValue / nextValue,
    };

    if (memory === null) {
      setMemory(nextValue);
    } else if (pendingOperator) {
      const currentValue = memory || 0;
      const computedValue = operations[pendingOperator](currentValue, nextValue);

      setMemory(computedValue);
      setDisplay(String(computedValue));
    }

    setWaitingForOperand(true);
    setPendingOperator(nextOperator);
  };

  const performSpecialOperation = (operation) => {
    const currentValue = parseFloat(display);
    let result;

    switch (operation) {
      case 'sin':
        result = Math.sin(currentValue * Math.PI / 180);
        break;
      case 'cos':
        result = Math.cos(currentValue * Math.PI / 180);
        break;
      case 'tan':
        result = Math.tan(currentValue * Math.PI / 180);
        break;
      case 'sqrt':
        result = Math.sqrt(currentValue);
        break;
      case 'log':
        result = Math.log10(currentValue);
        break;
      case 'ln':
        result = Math.log(currentValue);
        break;
      default:
        return;
    }

    setDisplay(String(result));
    setWaitingForOperand(true);
  };

  return (
    <Card className="w-96">
      <CardContent className="p-4">
        <div className="mb-4">
          <input
            type="text"
            value={display}
            readOnly
            className="w-full text-right p-2 text-2xl bg-gray-100 rounded"
          />
        </div>

        <div className="grid grid-cols-4 gap-2">
          <Button onClick={() => performSpecialOperation('sin')} variant="outline" className="text-sm">sin</Button>
          <Button onClick={() => performSpecialOperation('cos')} variant="outline" className="text-sm">cos</Button>
          <Button onClick={() => performSpecialOperation('tan')} variant="outline" className="text-sm">tan</Button>
          <Button onClick={() => performSpecialOperation('sqrt')} variant="outline" className="text-sm">√</Button>

          <Button onClick={() => performSpecialOperation('log')} variant="outline" className="text-sm">log</Button>
          <Button onClick={() => performSpecialOperation('ln')} variant="outline" className="text-sm">ln</Button>
          <Button onClick={() => setDisplay(String(Math.PI))} variant="outline" className="text-sm">π</Button>
          <Button onClick={() => setDisplay(String(Math.E))} variant="outline" className="text-sm">e</Button>

          <Button onClick={() => inputDigit(7)}>7</Button>
          <Button onClick={() => inputDigit(8)}>8</Button>
          <Button onClick={() => inputDigit(9)}>9</Button>
          <Button onClick={() => performOperation('÷')} variant="secondary">÷</Button>

          <Button onClick={() => inputDigit(4)}>4</Button>
          <Button onClick={() => inputDigit(5)}>5</Button>
          <Button onClick={() => inputDigit(6)}>6</Button>
          <Button onClick={() => performOperation('×')} variant="secondary">×</Button>

          <Button onClick={() => inputDigit(1)}>1</Button>
          <Button onClick={() => inputDigit(2)}>2</Button>
          <Button onClick={() => inputDigit(3)}>3</Button>
          <Button onClick={() => performOperation('-')} variant="secondary">-</Button>

          <Button onClick={() => inputDigit(0)}>0</Button>
          <Button onClick={inputDecimal}>.</Button>
          <Button onClick={() => performOperation('=')} variant="default">=</Button>
          <Button onClick={() => performOperation('+')} variant="secondary">+</Button>

          <Button onClick={clearAll} variant="destructive" className="col-span-4">Clear</Button>
        </div>
      </CardContent>
    </Card>
  );
};

export default ScientificCalculator;

 

반응형