Vue의 계산된 속성(computed) 내 get()과 set() 메서드 설명

반응형

이 코드에서 orderAmount는 Vue의 계산된 속성(computed property)으로, get()과 set() 메서드를 가진 객체를 사용해 정의되었습니다. 이 설계는 사용자 입력값과 표시값을 다르게 처리하기 위한 중요한 패턴입니다.

get() 메서드 — 값을 읽을 때 호출됩니다.

get() {
  return orderAmountRaw.value ? orderAmountRaw.value.toLocaleString() : '';
}
  • 역할: 화면에 값을 '표시'할 때 사용됩니다.
  • 동작 방식:
    • orderAmountRaw.value가 존재하면 숫자에 콤마를 추가하여 가독성 있게 표시(toLocaleString())
    • 값이 없으면 빈 문자열('')을 반환
  • 사용 시점: 템플릿에서 {{ orderAmount }}나 v-model="orderAmount"로 값을 읽을 때 호출됩니다.

set() 메서드 — 값을 변경할 때 호출됩니다.

set(val) {
  const onlyNumber = String(val).replace(/[^0-9]/g, '');
  orderAmountRaw.value = onlyNumber ? parseInt(onlyNumber, 10) : 0;
}
  • 역할: 사용자가 입력한 값을 '처리'할 때 사용됩니다.
  • 동작 방식:
    • 입력값을 문자열로 변환(String(val))
    • 정규식으로 숫자가 아닌 모든 문자 제거(replace(/[^0-9]/g, ''))
    • 숫자만 남은 문자열을 정수로 변환(parseInt())
    • 결과값을 실제 데이터인 orderAmountRaw에 저장
  • 사용 시점: v-model="orderAmount"가 적용된 입력 필드에 사용자가 값을 입력할 때 호출됩니다.
orderAmount.value = '200000';

이런 식으로 코드에서 직접 값을 설정할 때도 set()이 실행됩니다.

이 패턴의 장점

  1. 데이터 표시와 저장 분리:
    • 표시: 사용자에게는 콤마가 포함된 읽기 쉬운 형태(예: "1,000,000")
    • 저장: 내부적으로는 계산에 적합한 숫자 형태(예: 1000000)
  2. 자동 데이터 정제:
    • 사용자가 "1,000,000", "1000000", "1,000,000원" 등 다양한 형태로 입력해도
    • 항상 숫자만 추출하여 일관된 형태로 저장
  3. 양방향 바인딩 활용:
    • v-model 디렉티브로 입력과 출력을 모두 관리
    • 사용자 입력 → set() → 데이터 저장 → get() → 화면에 표시되는 사이클 자동화

이 구현은 특히 통화, 수량 등의 숫자 입력에서 유용하며, 사용자 경험을 향상시키면서도 내부 로직에서는 일관된 데이터 형식을 유지할 수 있게 해줍니다.

전체소스

<template>
    <div>
        <h2>바로환전</h2>
        <!-- 탭 버튼 -->
        <div>
            <button :class="{ active: selectedTab === 'market' }" @click="selectedTab = 'market'">시장가</button>
            <button :class="{ active: selectedTab === 'limit' }" @click="selectedTab = 'limit'">지정가</button>
            <button :class="{ active: selectedTab === 'auto' }" @click="selectedTab = 'auto'">자동주문</button>
        </div>

        <!-- 시장가 내용 -->
        <div v-if="selectedTab === 'market'">
            <h3>시장가</h3>
            <p>
                {{ isTokenOrder ? 'KRW 금액' : `${exchangeData.baseCurrency} 팔기` }}
                <input type="text" v-model="orderAmount" placeholder="주문 금액 입력" class="order-input" />
                <button @click="toggleOrderType" :class="{ on: isTokenOrder, off: !isTokenOrder }">
                    {{ isTokenOrder ? 'ON' : 'OFF' }}
                </button>
            </p>
            <p>출금가능금액: {{ (exchangeData.availableBalance / 10000).toLocaleString() }}만원</p>
            <div>
                <button @click="setMax">최대</button>
                <button @click="addAmount(1000000)">+1M</button>
                <button @click="addAmount(100000)">+0.1M</button>
            </div>
            <div class="conversion-info">
                <p>USD 환산: <span>USD 0.00</span></p>
                <p>KRW 환산: <span>0원</span></p>
            </div>
            <button @click="order">주문하기</button>
        </div>
        <!-- 지정가/자동주문 등도 동일하게 확장 가능 -->
    </div>
</template>

<script setup>
import { ref, computed } from 'vue';

const { exchangeData } = defineProps({
  exchangeData: Object,
});
const emit = defineEmits(['order']);

const selectedTab = ref('market');
const isTokenOrder = ref(false);

// 연산용 실제 값(숫자)
const orderAmountRaw = ref(0);

// 화면 표시용(콤마 포함)과 연산용(숫자)을 캡슐화
const orderAmount = computed({
  get() {
    // 화면에 표시할 때 콤마 추가
    return orderAmountRaw.value ? orderAmountRaw.value.toLocaleString() : '';
  },
  set(val) {
    // 입력값에서 숫자만 추출해서 저장
    const onlyNumber = String(val).replace(/[^0-9]/g, '');
    orderAmountRaw.value = onlyNumber ? parseInt(onlyNumber, 10) : 0;
  }
});

// input에 v-model로 바로 사용
// <input type="text" v-model="orderAmount" ... />

const toggleOrderType = () => {
  isTokenOrder.value = !isTokenOrder.value;
};
const setMax = () => {
  orderAmountRaw.value = 1000000;
};
const addAmount = amount => {
  orderAmountRaw.value += amount;
};

orderAmount.value = '2000000';

const order = () => {
  emit('order', {
    type: selectedTab.value,
    amount: orderAmountRaw.value, // 연산용 값만 사용
    isTokenOrder: isTokenOrder.value,
  });
};
</script>

 

반응형