Vue 3 + AG Grid 페이징처리
Posted by Albert 16Day 6Hour 49Min 28Sec ago [2026-01-21]
1.Infinite Row Model
전체 폴더 구조
src/ ├── pages/ │ └── UserListPage.vue │ ├── components/ │ └── grid/ │ └── UserGrid.vue │ ├── composables/ │ └── useAgGridInfinite.js │ ├── stores/ │ └── userStore.js │ ├── services/ │ └── userService.js │ └── main.js
1️⃣ services/
“API 통신만 담당” (비즈니스 로직 ❌)
services/userService.js
export async function fetchUsers({ start, limit, sortModel, filterModel }) {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
start,
limit,
sort: sortModel,
filter: filterModel
})
})
return response.json()
}
✔ 이 파일의 특징
- axios / fetch 교체 쉬움
- 테스트 가능
- Grid / Pinia 모름
2️⃣ stores/
“Grid에서 사용할 상태 & 액션”
stores/userStore.js
import { defineStore } from 'pinia'
import { fetchUsers } from '@/services/userService'
export const useUserStore = defineStore('user', {
state: () => ({
totalCount: 0
}),
actions: {
async loadUsers(params) {
const result = await fetchUsers(params)
this.totalCount = result.total
return result.rows
}
}
})
✔ 여기서 하는 일
- API 호출 위임
- totalCount 상태 관리
- 비즈니스 흐름 담당
3️⃣ composables/
“AG Grid Infinite 로직 캡슐화 (가장 중요 ⭐⭐⭐)”
composables/useAgGridInfinite.js
import { useUserStore } from '@/stores/userStore'
export function useAgGridInfinite() {
const userStore = useUserStore()
const dataSource = {
async getRows(params) {
try {
const rows = await userStore.loadUsers({
start: params.startRow,
limit: params.endRow - params.startRow,
sortModel: params.sortModel,
filterModel: params.filterModel
})
params.successCallback(rows, userStore.totalCount)
} catch (e) {
params.failCallback()
}
}
}
return {
dataSource
}
}
✔ 왜 composables 인가?
- Grid 재사용 가능
- store / service 조합
- 페이지와 Grid 분리
Infinite Row Model의 핵심은 여기 있음
4️⃣ components/
“AG Grid UI 전용 컴포넌트”
components/grid/UserGrid.vue
<template>
<div class="ag-theme-alpine" style="height: 600px">
<AgGridVue
rowModelType="infinite"
:columnDefs="columnDefs"
:datasource="dataSource"
:cacheBlockSize="100"
:maxBlocksInCache="5"
:rowHeight="40"
@grid-ready="onGridReady"
/>
</div>
</template>
<script setup>
import { AgGridVue } from 'ag-grid-vue3'
import { useAgGridInfinite } from '@/composables/useAgGridInfinite'
const { dataSource } = useAgGridInfinite()
const columnDefs = [
{ field: 'id', headerName: 'ID', width: 80 },
{ field: 'name', headerName: '이름' },
{ field: 'email', headerName: '이메일' }
]
const onGridReady = (params) => {
params.api.setDatasource(dataSource)
}
</script>
✔ 역할
- UI만 담당
- API / store 모름
- Infinite 설정 집중
5️⃣ pages/
“페이지 단위 조립”
pages/UserListPage.vue
<template>
<div>
<h2>사용자 목록</h2>
<UserGrid />
</div>
</template>
<script setup>
import UserGrid from '@/components/grid/UserGrid.vue'
</script>
✔ 페이지에서는
- 비즈니스 몰라도 됨
- Grid 컴포넌트 조립만
6️⃣ 데이터 흐름 한눈에 보기
UserListPage ↓ UserGrid ↓ useAgGridInfinite (composable) ↓ userStore (Pinia) ↓ userService (API) ↓ Server
7️⃣ 이 구조의 장점 (실무에서 선택되는 이유)
항목이유대용량 성능Infinite Row Model유지보수책임 분리재사용성composable 중심테스트service 분리확장성다른 Grid 쉽게 추가
실무 꿀팁 (중요)
✔ Infinite Grid는 반드시
rowHeight: 40 animateRows: false
❌ 절대 하지 말 것
rowData = hugeArray
✅ 최종 정리 한 줄
AG Grid Infinite Row Model의 정석 구조는
composables에서 datasource, stores에서 상태, services에서 API다
2. Client-Side Row Model + pagination
전체 폴더 구조
src/ ├── pages/ │ └── UserListPage.vue │ ├── components/ │ └── grid/ │ └── UserGridClient.vue │ ├── composables/ │ └── useAgGridClient.js │ ├── stores/ │ └── userStore.js │ ├── services/ │ └── userService.js │ └── main.js
1️⃣ services/
서버에서 “전체 데이터”를 한 번에 가져옴
services/userService.js
export async function fetchAllUsers() {
const response = await fetch('/api/users/all')
return response.json()
}
서버 응답 예시
[
{ "id": 1, "name": "Alice", "email": "a@test.com" },
{ "id": 2, "name": "Bob", "email": "b@test.com" }
]
2️⃣ stores/
전체 데이터를 메모리에 보관
stores/userStore.js
import { defineStore } from 'pinia'
import { fetchAllUsers } from '@/services/userService'
export const useUserStore = defineStore('user', {
state: () => ({
users: []
}),
actions: {
async loadAllUsers() {
this.users = await fetchAllUsers()
}
}
})
✔ Client-Side 방식의 핵심
→ store에 전체 데이터 보관
3️⃣ composables/
AG Grid 옵션 & pagination 설정 담당
composables/useAgGridClient.js
export function useAgGridClient() {
const columnDefs = [
{ field: 'id', headerName: 'ID', width: 80 },
{ field: 'name', headerName: '이름' },
{ field: 'email', headerName: '이메일' }
]
const gridOptions = {
pagination: true,
paginationPageSize: 20,
rowHeight: 40,
animateRows: false
}
return {
columnDefs,
gridOptions
}
}
✔ 여기서는
- pagination 활성화
- page size 설정
4️⃣ components/
Grid UI 전용 컴포넌트
components/grid/UserGridClient.vue
<template>
<div class="ag-theme-alpine" style="height: 600px">
<AgGridVue
:rowData="rowData"
:columnDefs="columnDefs"
:gridOptions="gridOptions"
/>
</div>
</template>
<script setup>
import { AgGridVue } from 'ag-grid-vue3'
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/userStore'
import { useAgGridClient } from '@/composables/useAgGridClient'
const userStore = useUserStore()
const { users: rowData } = storeToRefs(userStore)
const { columnDefs, gridOptions } = useAgGridClient()
</script>
✔ 특징
- rowModelType ❌ (기본값 = clientSide)
- pagination 버튼 자동 생성
5️⃣ pages/
페이지 진입 시 전체 데이터 로딩
pages/UserListPage.vue
<template>
<div>
<h2>사용자 목록 (Client-Side Pagination)</h2>
<UserGridClient />
</div>
</template>
<script setup>
import { onMounted } from 'vue'
import { useUserStore } from '@/stores/userStore'
import UserGridClient from '@/components/grid/UserGridClient.vue'
const userStore = useUserStore()
onMounted(() => {
userStore.loadAllUsers()
})
</script>
6️⃣ 데이터 흐름
UserListPage (onMounted) ↓ userStore.loadAllUsers() ↓ userService.fetchAllUsers() ↓ store.users (전체 데이터) ↓ AG Grid (pagination 처리)
7️⃣ 화면에서 자동으로 생기는 것들
AG Grid가 자동 제공
- ⬅ 이전 / 다음 페이지
- 페이지 번호
- 페이지당 row 수
[ < ] 1 2 3 4 [ > ]