안녕하세요.
이번 글에서는 지난 포스팅에서 구현한 실시간 코인 시세 사이트에 WebSocket을 활용한 실시간 데이터를 구현하고, 데이터를 가공하고 변화를 감지하여 UI에 반영하는 방법을 함께 살펴보겠습니다.
목차
- WebSocket을 활용한 실시간 데이터 구현
- 데이터 가공과 UI 수정
1. WebSocket을 활용한 실시간 데이터 구현
실습을 진행하기 전, Vue의 Lifecycle Hooks에 대해 알아보도록 하겠습니다.
| Lifecycle Hooks
Vue에서는 컴포넌트가 생성되고 제거되는 일련의 흐름(생명주기)에 따라 특정 시점마다 작업을 수행할 수 있도록 라이프사이클 훅이라는 개념을 제공합니다.
Composition API에서는 각 훅을 함수 형태로 가져와서 실행하며, 컴포넌트의 생성, 마운트, 업데이트, 언마운트 등 다양한 시점에 적절한 로직을 넣을 수 있습니다.
- onMounted : 컴포넌트가 DOM에 마운트된 직후 실행됨
- onUnmounted : 컴포넌트가 마운트 해제된 후 실행됨
- onBeforeMount : 컴포넌트가 마운트되기 직전에 실행됨
- onBeforeUnmount : 컴포넌트가 마운트 해제되기 직전에 실행됨
- onUpdated : 컴포넌트의 DOM이 업데이트된 후 실행됨
이처럼 Vue 3의 라이프사이클 훅은 컴포넌트의 흐름을 따라 코드의 실행 시점을 세밀하게 제어할 수 있는 중요한 도구입니다.
WebSocket 연결은 onMounted 훅을 활용해 컴포넌트가 화면에 표시된 직후 실행 되도록 구성합니다. 컴포넌트가 제거된 직후에 실행되는 onUnmounted훅에서는 WebSocket을 종료하여 리소스 낭비를 방지합니다. 수신한 WebSocket 데이터를 저장할 반응형 객체를 스토어에 추가한 뒤, 데이터 수신시 해당 객체에 갱신합니다.
- store : stores/upbit.js
const productsTicket = ref({}) // 응답값을 저장할 객체를 추가합니다.
return {
consts,
productGroup,
productsTicket, // 추가한 객체를 return합니다.
codes,
getProducts
}
- components : productList.vue
const { productGroup, productsTicket } = storeToRefs(upbitStore) //productTicket을 추가합니다.
let socket = null
onMounted(async () => {
await upbitStore.getProducts(); // onMounted 내부로 이동합니다.
socket = new WebSocket("wss://api.upbit.com/websocket/v1");
socket.onopen = () => {
socket.send(
`${JSON.stringify([
{ ticket: "test example" },
{ type: "ticker", codes: getCodes(productGroup.value['KRW']) },
{ format: "DEFAULT" },
])}`
);
socket.onmessage = async (data) => {
const ticket = await new Response(data.data).json();
// 실시간 가격 데이터를 code별로 저장합니다.
productsTicket.value[ticket.code] = {
code: ticket.code,
ticket: ticket,
};
};
};
});
onUnmounted(() => {
if (socket && socket.readyState === WebSocket.OPEN) {
socket.close()
}
})
// 코드 목록을 만들어 반환합니다.
function getCodes(list = []) {
const codes = []
for (let code in list) {
codes.push(code)
}
return codes
}
자식 컴포넌트에서는 props를 사용하지 않더라도 같은 스토어를 import하여 상태값을 직접 사용할 수 있습니다.
WebSocket으로부터 전달된 데이터가 스토어 내부 상태에 반영되면, 이를 참조하는 템플릿 내 {{변수명}} 형태로 바인딩된 값에도 자동으로 반영되어 실시간으로 UI가 업데이트됩니다.
스토어 내부 객체에서 특정 종목의 키 값으로 접근하여 현재가를 가져옵니다. 이 때 옵셔널 체이닝(?.)을 활용하면 데이터가 아직 존재하지 않는 경우에도 에러 없이 안전하게 렌더링할 수 있습니다.
- components : productItem.vue
// 스토어 내부 상태값 사용을 위해 추가합니다.
const upbitStore = useUpbitStore()
const { productsTicket } = storeToRefs(upbitStore)
// 값을 바인딩합니다.
{{productsTicket[props.product.key]?.ticket?.trade_price }} //현재가
{{productsTicket[props.product.key]?.ticket?.signed_change_rate}} //전일대비
WebSocket을 활용한 실시간 데이터 구현이 완료되었습니다. 다음 단계에서는 사용자들에게 더 직관적으로 보여줄 수 있도록 수신한 데이터를 가공하고 UI를 개선해보겠습니다.
2-1. computed를 활용한 데이터 포맷팅과 스타일 동적 적용
실습에 앞서 computed 속성에 대해 알아보겠습니다.
| computed
Vue에서 사용하는 계산된 속성으로 기존의 반응형 데이터를 기반으로 파생 데이터를 선언적으로 정의할 수 있는 기능입니다.
내부적으로 의존성 추적과 캐싱 기능이 함께 동작하기 때문에 불필요한 연산을 줄이고 효율적인 렌더링을 가능하게 합니다.
computed 속성을 사용해 store에 저장된 실시간 데이터를 가공하겠습니다. 가격이 1보다 크면 천 단위 콤마가 포함된 문자열로 변환하고, 1 이하일 경우에는 소수점 둘째 자리까지 고정된 값으로 반환합니다.
const priceValue = computed(() => {
const price = productsTicket.value[props.product.key]?.ticket.trade_price
if (!price) {
return "0.00"
}
if (price > 1) {
return price.toLocaleString("en")
}
const fixedValue = 2
return price.toFixed(fixedValue)
})
전일 대비를 %로 변환하여 소수점 두자리까지 표현합니다.
const rateValue = computed(() => {
const rate = productsTicket.value[props.product.key]?.ticket.signed_change_rate
if (!rate) {
return '0.00'
}
return (rate.toFixed(8) * 100).toFixed(2)
})
상승, 하락, 보합에 따라 텍스트의 색상을 변경하여 적용합니다.
const textClass = computed(() => {
const change = productsTicket.value[props.product.key]?.ticket.change;
if (!change) {
return "";
}
if (change === "RISE") {
return "text-danger";
} else if (change === "FALL") {
return "text-primary";
} else if (change === "EVEN") {
return "";
}
});
computed를 통해 계산된 값을 템플릿 문법({{ }})을 통해 바인딩합니다.
클래스에 사용할 값은 :class(v-bind:class)를 통해 동적으로 적용할 수 있습니다.
<td class="alignRight" :class="textClass">
<div>{{ priceValue }}</div>
<em></em>
</td>
<td class="alignRight" :class="textClass">
{{ rateValue }}
</td>
2-2. watch를 활용한 UI 효과
마지막으로, 가격이 변동되었을 때 사용자가 인식하기 쉽도록 주변에 박스 형태로 깜빡이는 blink 효과를 추가해 보겠습니다. 가격이 변동되었을 때를 감지하기 위해서 watch를 활용합니다.
| watch
Vue에서 특정 반응형 데이터의 변화를 감지하고 그 값이 변경될 때마다 지정된 로직을 실행할 수 있도록 도와주는 반응형 감시 기능으로 비동기 처리, DOM 조작 등 다양한 후속 동작을 실행할 수 있습니다.
Compositon API에서는 watch()라는 함수를 통해 감시 로직을 구현합니다. 이번 예제에서는 watch()를 통해 실시간 가격 데이터를 감시하고 이전 가격과 새 가격이 달라질 때만 blink 효과가 동작하도록 구현해보겠습니다.
- components : productItem.vue
// 추가
const blinkRed = ref(false)
const blinkBlue = ref(false)
watch(() => productsTicket.value[props.product.key]?.ticket.trade_price, (newPrice, oldPrice) => {
if (oldPrice && oldPrice != newPrice) {
const rate = productsTicket.value[props.product.key]?.ticket.signed_change_rate
if (rate && rate > 0) {
blinkRedPriceBox()
return
}
blinkBluePriceBox()
}})
function blinkRedPriceBox() {
if (!blinkRed.value) {
blinkBlue.value = false
blinkRed.value = true
}
setTimeout(() => {
blinkRed.value = false
}, 200)
}
function blinkBluePriceBox() {
if (!blinkBlue.value) {
blinkRed.value = false
blinkBlue.value = true
}
setTimeout(() => {
blinkBlue.value = false
}, 200)
}
스타일을 적용하기 위해 highlight-red, highlight-blue와 같은 클래스를 미리 정의해두고 :class 디렉티브(v-bind:class)를 통해 값을 적용합니다. blinkRed 또는 blinkBlue의 값이 true일때만 해당 클래스가 동적으로 적용되어 일시적으로 박스 테두리가 깜빡이는 blink 효과를 주게 됩니다.
.highlight-red {
border: 1px solid red !important;
}
.highlight-blue {
border: 1px solid blue !important;
}
<div :class="{ 'highlight-red': blinkRed, 'highlight-blue': blinkBlue }">
{{ priceValue }}
</div>
이번 실습은 여기서 마무리하겠습니다. 지금까지 구현한 기능을 바탕으로 기능 추가와 성능 최적화, 에러 처리, 디자인 개선을 통해 프로젝트를 한 단계 더 발전시키는 것은 좋은 연습이 될 것입니다. 아래는 예제를 바탕으로 탭별 필터링과 검색기능을 구현하고 개선시킨 예시입니다.
Vue의 다양한 기능을 더 깊이 이해하고 싶다면 https://vuejs.org/ 도 함께 참고해보시기 바랍니다.
전편의
[Vue.js 3.0] (1) Vue 3 + UPbit API로 실시간 코인 시세 사이트 만들기 (1)를 보고 싶으시다면
아래의 URL을 클릭해주세요~
[Vue.js 3.0] Vue 3 + UPbit API로 실시간 코인 시세 사이트 만들기 (1)
안녕하세요.이번 글에서는 앞선 포스팅에서 소개한 Vue 3와 UPbit API를 활용하여 간단한 예제를 직접 구현해보겠습니다.실습 과정에서 Vue의 컴포넌트 구조와 컴포넌트간의 상호작용 방식에 대해
cyberx.tistory.com
'프론트엔드' 카테고리의 다른 글
[React] React + Upbit API로 실시간 코인 시세 사이트 만들기 (1) (1) | 2025.07.04 |
---|---|
[React] React 소개 및 프로젝트 생성 (0) | 2025.06.27 |
[Vue.js 3.0] Vue 3 + UPbit API로 실시간 코인 시세 사이트 만들기 (1) (0) | 2025.04.22 |
[Vue.js 3.0] (2) Navigation Guard, Pinia 활용기 (0) | 2024.01.22 |
[Vue.js 3.0] (1) Vue 소개 및 프로젝트 생성 (0) | 2024.01.15 |