
Nuxt3에서 페이지네이션 만들기
Nuxt안녕하세요 Lovefield입니다.
이번 글은 페이지네이션에 관한 글입니다. 페이지네이션은 게시글 혹은 정보가 있는 페이지라면 거의 빠짐없이 들어가게 되는 기능중 하나입니다. 그만큼 기본적인 기능이죠.
우선 components 폴더에 Pagination.vue 파일을 생성합니다.
Pagination.vue
HTML, XML
<template>
<div class=”pagination”></div>
</template>
<script setup lang=”ts”>
</script>
페이지 정보를 연산하기 위해서 현재 활성화된 페이지 정보와 전체 페이지 정보가 필요합니다. defineProps를 이용해 해당 정보를 받도록 합니다. 추가로 전체 페이지가 1보다 큰 경우만 페이지 네이션을 표기하도록 합니다. 그리고 페이지를 이동하기 위해 필요한 URL 정보도 전달받습니다. URL정보는 “/board/$” 형태로 전달받아 사용합니다.
HTML, XML
<template>
<div v-if="props.totalPage > 1" class=”pagination”></div>
</template>
<script setup lang=”ts”>
interface PagenationData {
url: string;
active: number;
totalPage: number;
}
const props = defineProps<PagenationData>();
</script>
연산한 정보를 저장할 변수를 지정하고, totalPage가 변경되거나 컴포넌트가 로드되었을때 연산을 하도록 설정합니다.
TypeScript
const step = ref<number>(0);
const totalStep = ref<number>(0);
const startNumber = ref<number>(1);
const numberList = ref<number[]>([]);
const itemCount: number = 5;
function init(): void {
}
init();
watch(
() => props.totalPage,
() => {
init();
}
);
페이지네이션에 1부터 5까지를 표기한다고 가정합니다. 이는 itemCount의 값과 동일합니다. 이때 1부터 5의 범위가 하나의 step이 됩니다. 활성화된 페이지 값을 기준으로 step을 산정한다음 화면에 랜더링하기 위해 필요한 값들을 계산합니다.
TypeScript
function init(): void {
const stepData: number = Math.ceil(props.active / itemCount);
const totalStepData: number = Math.ceil(props.totalPage / itemCount);
const startNumberData: number = stepData * itemCount - (itemCount - 1);
const numberListData: number[] = [];
for (let i: number = startNumberData; i < startNumberData + itemCount; i += 1) {
if (i > props.totalPage) {
break;
}
numberListData.push(i);
}
step.value = stepData;
totalStep.value = totalStepData;
startNumber.value = startNumberData;
numberList.value = numberListData;
}
화면을 렌더링 하기 위한 값들을 전부 계산 했으니 이제 렌더링을 구성합니다. 이전 step과 다음 step으로 넘어가는 버튼과 1부터 5까지의 버튼을 구성합니다. 각각 버튼을 클릭했을때 해당하는 페이지로 넘어갈 수 있도록 movePageEvent 함수를 지정합니다.
HTML, XML
<template>
<div v-if="props.totalPage > 1" class="pagination">
<button class="link" :disabled="step === 1" @click="movePageEvent(startNumber - 1)"><</button>
<button
v-for="(item, count) in numberList"
class="link"
:class="{ '--active': item === props.active }"
:key="`btn-page${count}`"
@click="movePageEvent(item)"
>
{{ item }}
</button>
<button class="link" :disabled="step === totalStep" @click="movePageEvent(startNumber + itemCount)">
>
</button>
</div>
</template>
페이지를 이동하기위한 함수를 작성합니다. props로 받은 url을 기준으로 $를 페이지 숫자로 전환해 이동시켜줍니다.
TypeScript
const route = useRoute();
async function movePageEvent(idx: number): Promise<void> {
await navigateTo({
path: props.url.replace("$", String(idx)),
query: route.query,
replace: true,
});
}
컴포넌트 구성이 완료되었습니다. 이제 다음과 같이 컴포넌트를 사용할 수 있습니다.
HTML, XML
<Pagination url="/board/free/$`" :active="1" :total="50" />
아래는 최종 컴포넌트 코드입니다.
Pagination.vue
HTML, XML
<template>
<div v-if="props.totalPage > 1" class="pagination">
<button class="link" :disabled="step === 1" @click="movePageEvent(startNumber - 1)"><</button>
<button
v-for="(item, count) in numberList"
class="link"
:class="{ '--active': item === props.active }"
:key="`btn-page${count}`"
@click="movePageEvent(item)"
>
{{ item }}
</button>
<button class="link" :disabled="step === totalStep" @click="movePageEvent(startNumber + itemCount)">
>
</button>
</div>
</template>
<script setup lang="ts">
interface PagenationData {
url: string;
active: number;
totalPage: number;
}
const route = useRoute();
const props = defineProps<PagenationData>();
const step = ref<number>(0);
const totalStep = ref<number>(0);
const startNumber = ref<number>(1);
const numberList = ref<number[]>([]);
const itemCount: number = 5;
function init(): void {
const stepData: number = Math.ceil(props.active / itemCount);
const totalStepData: number = Math.ceil(props.totalPage / itemCount);
const startNumberData: number = stepData * itemCount - (itemCount - 1);
const numberListData: number[] = [];
for (let i: number = startNumberData; i < startNumberData + itemCount; i += 1) {
if (i > props.totalPage) {
break;
}
numberListData.push(i);
}
step.value = stepData;
totalStep.value = totalStepData;
startNumber.value = startNumberData;
numberList.value = numberListData;
}
init();
async function movePageEvent(idx: number): Promise<void> {
await navigateTo({
path: props.url.replace("$", String(idx)),
query: route.query,
replace: true,
});
}
watch(
() => props.totalPage,
() => {
init();
}
);
</script>