안녕하세요.
이번 글에서는 앞선 포스팅에서 소개한 Vue 3와 UPbit API를 활용하여 간단한 예제를 직접 구현해보겠습니다.
실습 과정에서 Vue의 컴포넌트 구조와 컴포넌트간의 상호작용 방식에 대해서도 함께 알아보겠습니다.
목차
- 프로젝트 세팅과 UI 구성
- UPbit API 연동하여 종목명 표시하기
1-1. 프로젝트 생성 및 VSCode 확장 프로그램 소개
이전 포스팅에서 Node.js 설치와 프로젝트 생성 방법을 다뤘습니다.
이번에는 동일한 방식으로 Vue 프로젝트를 만들고, 로컬 환경에서 실행해보겠습니다.
[Vue.js 3.0] (1) Vue 소개 및 프로젝트 생성
안녕하세요. 이번 글에서는 웹 개발에서 중요한 역할을 하는 프론트엔드 프레임워크 Vue.js를 설명드리겠습니다. 우선 Vue.js에 대해 알아보고 프로젝트를 생성하며 간단한 예제를 만들어보고, 다
cyberx.tistory.com
프로젝트를 시작하기 전에, 개발 환경을 효율적으로 구성하는데 도움을 줄 수 있는 VSCode 확장 프로그램을 소개합니다.
| VSCode 확장프로그램 소개
1) Vue – Official(Volar) : Vue 팀에서 공식적으로 제공하는 Vue 3 개발용 확장 (TypeScript 지원 포함)
2) ESLint : 코드 문법 및 스타일 검사
3) Prettier : 코드 포맷 정리 자동화
4) Vue 3 Snippets : Vue3에 특화된 자동완성 제공
위와 같은 확장프로그램을 설치하면 코드 자동완성, 문법 오류 표시, 자동 포맷팅 등 다양한 기능을 통해 개발 생산성을 향상시킬 수 있습니다. 특히 Vue3를 지원하는 확장 프로그램들을 통해 최신 문법을 지원받고 일관된 코드 스타일을 유지할 수 있습니다.
1-2. UI 구성
UI 구성에 앞서, Vue 프로젝트에서는 컴포넌트를 역할에 따라 views/와 components/ 로 나누어 관리하는 것이 일반적입니다.
| views/와 components/의 차이
views/ : 페이지 단위 컴포넌트로 라우터에 직접 연결되는 대상. 화면 전체를 구성하며 여러 개의 하위 컴포넌트를 조합함
components/ : 재사용 가능한 UI로 라우터에 직접 연결되지 않으며, 다양한 화면에서 재사용이 가능함
이처럼 컴포넌트를 목적에 따라 나누는 방식은 프로젝트 구조가 체계적으로 정리되어 각 컴포넌트의 역할이 명확해지고 재사용 가능한 UI요소를 따로 분리함으로써 동일한 기능을 여러 화면에서 활용할 수 있어 개발 효율이 향상되는 장점을 가집니다.
또한 각 컴포넌트가 독립적으로 동작하도록 구성되기 때문에 기능을 수정하거나 추가하더라도 다른 부분에 영향을 최소화할 수 있기 때문에 유지보수가 수월합니다.
이번 예제에서도 views/ 디렉토리에 메인 페이지를, components/ 디렉토리에 테이블 영역 컴포넌트를 분리하여 생성하겠습니다.
- 메인 페이지 컴포넌트 생성 : views/upbit/upbitList.vue
- 테이블 컴포넌트 생성 : components/products/productList.vue
upbitList.vue 파일의 예시코드입니다.
테이블 영역은 <ProductList /> 컴포넌트를 사용해 화면에 출력합니다.
<template>
<div>
<section id="upbit">
<ProductList />
</section>
</div>
</template>
<script setup>
import ProductList from "@/components/products/productList.vue";
</script>
upbitList.vue 파일은 메인 페이지 역할을 하므로 Vue Router를 통해 해당 컴포넌트를 URL 경로에 연결합니다. 이를 위해 src/router/index.js 파일에 라우팅 설정을 추가합니다. 아래 예시는 초기 진입시 /main으로 이동한 코드입니다.
import { createRouter, createWebHistory } from 'vue-router'
import upbitList from '@/views/upbit/upbitList.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
redirect: { name: 'main' }
},
{
path: `/main`,
name: 'main',
component: upbitList,
props: true
},
],
})
export default router
App.vue 파일은 프로젝트의 루트 컴포넌트로 라우터를 통해 연결된 페이지를 출력하는 역할을 합니다. <router-view /> 태그를 통해 현재 경로에 해당하는 컴포넌트를 동적으로 렌더링합니다.
productList.vue 파일에는 테이블 마크업을 추가하고 스타일을 자유롭게 적용하여 UI구성을 완료합니다.
<template>
<div class="contentsArea">
<table class="checkTable">
<colgroup>
<col width="140px" />
<col width="110px" />
<col width="90px" />
</colgroup>
<thead>
<tr>
<th class="alignCenter" scope="col">한글명</th>
<th class="alignCenter" scope="col">현재가</th>
<th class="alignCenter" scope="col">전일대비</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<strong></strong>
</td>
<td class="alignRight">
</td>
<td class="alignRight">
</td>
</tr>
</tbody>
</table>
</div>
</template>
여기까지 메인 페이지와 테이블 컴포넌트를 분리하여 구성해보았습니다. 다음 단계에서는 업비트 API를 연동하여 실제 데이터를 불러오고, 테이블에 정보를 표시하는 기능을 구현해보겠습니다.
2-1. UPbit API 와 WebSocket 소개
이번 예제에서는 업비트에서 제공하는 종목 코드 조회 API와 현재가를 조회하는 WebSocket을 사용합니다. 연결된 사이트에서 요청과 응답 형식의 예시를 확인할 수 있습니다.
1) 종목 코드 조회
업비트 개발자 센터
docs.upbit.com
- 업비트에서 거래 가능한 종목 목록을 조회합니다
- API URL : https://api.upbit.com/v1/market/all
2) 실시간 시세 WebSocket
업비트 개발자 센터
docs.upbit.com
- 현재가를 조회합니다.
- WebSocket URL : wss://api.upbit.com/websocket/v1
2-2. 종목 코드 조회 API 호출 및 상태 관리 (axios + Pinia)
앞선 포스팅에서 소개한 Pinia와 axios 통신을 활용하여 API를 호출하고 해당 값을 전역에서 사용해볼 차례입니다.
axios는 별도의 파일을 만들고 API를 요청하는 로직은 api/ 디렉토리 하위에 별도 파일로 분리해 구성했습니다.
- axios : utils/axios.js 파일 생성
import axios from "axios";
export const awaitApi = async (method, url, _headers, data) => {
const axios = createAxios(_headers);
if (method == "GET") {
try {
const response = await axios.get(url);
const responseData = response.data;
return returnAwaitApi(responseData);
} catch (e) {
return returnAwaitApi(e);
}
} else if (method == "POST") {
try {
const response = await axios.post(url, data);
const responseData = response.data;
return returnAwaitApi(responseData);
} catch (e) {
return returnAwaitApi(e);
}
}
};
const returnAwaitApi = (data) => {
if (data instanceof Error) {
return {
success: false,
result: null,
error: data,
};
}
return {
success: true,
result: data,
error: false,
};
};
const createAxios = (_headers) => {
return axios.create({
headers: _headers,
});
};
- API : api/upbitApi.js 파일 생성
UPbit API종목 정보를 가져오기 위해 getProducts() 함수를 구현합니다.
import { awaitApi } from "@/utils/axios";
export default {
async getProducts() {
return await awaitApi("GET", "https://api.upbit.com/v1/market/all");
},
};
스토어를 생성하고 반응형 상태값과, api를 호출하여 값을 가공하는 함수를 생성합니다. 실습에서는 우선 KRW마켓의 종목만을 사용하겠습니다. KRW 마켓 종목은 객체 형태로 저장하여 종목 코드(key)로 빠르게 접근할 수 있도록 구성합니다.
- 스토어 생성 : stores/upbit.js
import { ref } from "vue";
import { defineStore } from "pinia";
import upbitApi from "@/api/upbitApi.js";
export const useUpbitStore = defineStore("upbitStore", () => {
const consts = {
markets: {
KRW: 'KRW'
}
}
const allProducts = ref([])
const krwProducts = ref({})
const productGroup = ref({
ALL: allProducts,
KRW: krwProducts
})
async function getProducts() {
const { result } = await upbitApi.getProducts();
allProducts.value = result.map((item) => {
return {
key: item.market,
item: item,
};
});
allProducts.value.forEach(item => {
const marketName = item.key.split('-')[0]
if (marketName === consts.markets.KRW) {
krwProducts.value[item.key] = item
}
})
}
return {
consts,
productGroup,
getProducts
}
});
2-3. 데이터바인딩
productList.vue 파일에서는 getProducts() 함수를 호출하여 종목 데이터를 불러옵니다. 이는 upbitStore에서 API를 통해 데이터를 가져오는 비동기 함수로 다음과 같이 사용됩니다.
import { useUpbitStore } from "@/stores/upbit.js";
const upbitStore = useUpbitStore()
await upbitStore.getProducts();
다음으로 각 종목의 데이터를 표시할 테이블의 행 영역을 별도의 컴포넌트로 분리합니다.
- 테이블 행 영역 컴포넌트 생성 : components/products/productItem.vue
분리된 컴포넌트에 데이터를 전달하기 위해 props를 사용합니다. 이는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 방식입니다.
| props란
부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 방식
- 단방향 데이터 흐름 (부모 -> 자식)
- 자식에서 수정 불가(읽기 전용)
- 타입 지정이 가능함(안전한 데이터 처리가 가능)
데이터를 컴포넌트 간에 직접 전달하는 방식에는 props와 emit이 있습니다. 두 방식의 차이를 간단히 비교해보고 앞서 사용한 전역 상태 관리 방식인 store와의 차이도 함께 살펴보겠습니다.
| props vs emit vs store
- props : 부모->자식으로 데이터를 전달하기 위함 :value=”data”형태로 명시하는 것이 필요
- emit : 자식->부모로 이벤트를 전달하기 위함 $emit(‘event’) 형식으로 사용
- store : 전역 상태로 접근이 가능하며 여러 컴포넌트에서 상태 공유를 목적으로 사용함 어디서든 직접 접근이 가능함
부모 컴포넌트인 productList.vue에서 자식 컴포넌트인productItem.vue로 :product=”product” 형식을 사용하여 개별종목 데이터를 props로 전달합니다. 자식 컴포넌트에서는 defineProps로 해당값을 전달받아 사용합니다.
- productList.vue
<tr v-for="(product, index) in productGroup['KRW']" :key="index">
<ProductItem :product="product" />
</tr>
// 스토어 값을 가져오기 위해 추가합니다.
import { useUpbitStore } from '@/stores/upbit.js'
import {storeToRefs} from "pinia";
const upbitStore = useUpbitStore()
const { productGroup } = storeToRefs(upbitStore)
- productItem.vue
<template>
<td>
<strong>{{ product.item.korean_name }}</strong>
</td>
<td class="alignRight">
</td>
<td class="alignRight">
</td>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { useUpbitStore } from "@/stores/upbit.js";
import {storeToRefs} from "pinia";
const props = defineProps({
product: {
type: Object,
}
});
</script>
이로써 전체 종목 중 KRW로 거래 가능한 종목을 필터링하여 종목명을 테이블에 표시하는 작업까지 완료하였습니다.
다음 시간에는 WebSocket을 통해 실시간 시세의 변동을 수신하고 테이블에 반영해 실시간 UI 업데이트를 구현해보겠습니다.
'프론트엔드' 카테고리의 다른 글
[React] React 소개 및 프로젝트 생성 (0) | 2025.06.27 |
---|---|
[Vue.js 3.0] Vue 3 + UPbit API로 실시간 코인 시세 사이트 만들기 (2) (0) | 2025.04.24 |
[Vue.js 3.0] (2) Navigation Guard, Pinia 활용기 (0) | 2024.01.22 |
[Vue.js 3.0] (1) Vue 소개 및 프로젝트 생성 (0) | 2024.01.15 |
Flutter (0) | 2020.05.07 |