본문 바로가기

Vue로 웹페이지나 모바일 페이지를 제작할 때 대부분 데이터베이스를 연동해서 불러와 사용하게 됩니다.
트래픽이 많지 않거나 용량을 많이 사용하지 않을 경우 또는 개발자가 테스트용으로 개발을 할때 Google의 Firebase는 매우 유용한 서비스 입니다. 사용량이 많지 않다면 무료로 사용을 할 수 있으닌까요.

만약 게시판의 리스트 형태로 데이터를 보여 줄 경우 일반적으로 페이지를 나누어서 데이터베이스에서 긁어와서 보여주는 것이 일반적입니다.

하지만 요즘 트랜드나 모바일에서는 옛날처럼 페이지를 나누는 것이 아니라 리스트의 가장 아래쪽으로 이동 했을 때 다음 페이지를 불러와서 보여주는 식으로 많이 구현을 하고 있습니다. 그런 것을 보통 infinite-scroll 또는 infinite-loading 이라고 하는데 Vue에서는 누군가 고마운 분들이 플러그인 형태로 몇가지 제공해 주고 있습니다. 그 중 asesome-vue에서 높은 추천을 받고 있는 Vue-infinite-loading 플러그인을 사용하여 firebase와 연동하여 구현해 보려고 합니다.

사용된 환경

  • Vue.js
  • Vuex
  • Vue-infinite-loading
  • Firebase(firestore)

Vuex를 사용하여 $store를 통해 DATA를 중앙집중관리로 개발하고 Vue-infinite-loading를 사용하여 데이터베이스인 Firestore의 자료를 순차적으로 불러오는 환경입니다.

$store 설정 (order.js)

아래는 전체코드입니다. 전체코드를 먼저 보여드리고 하나씩 설명하도록 하겠습니다.

import { DB } from "./firebaseConfig";

export const Orders = {
    state: {
        orderList: [],  
        fbStartAfter: null,  
    },

    getters: {
        orderList: (state) => {
            return state.orderList;
        },
        fbStartAfter: (state) => {
            return state.fbStartAfter;
        },
    },

    mutations: {
        MUT_SET_ORDER_LIST(state, orders) {
            state.orderList.push(...orders);
        },
        MUT_SET_startAfter(state, fbStartAfter) {
            state.fbStartAfter = fbStartAfter;
        },
    },

    actions: {
        ACT_GET_ORDER_LIST({ state, commit }, getMode) {
            return new Promise((resolve) => {
                let query;
                if (getMode == "first") {
                    query = DB.collection("orders").orderBy("date", "desc").limit(5);
                } else {
                    query = DB.collection("orders").orderBy("date", "desc").startAfter(state.fbStartAfter).limit(5);
                }
                query.get().then((querySnapshot) => {
                    if (querySnapshot.docs[0]) {
                        const fbStartAfter = querySnapshot.docs[querySnapshot.docs.length - 1];
                        const data = querySnapshot.docs.map((doc) => Object.assign({ id: doc.id }, doc.data()));
                        commit("MUT_SET_ORDER_LIST", data);
                        commit("MUT_SET_startAfter", fbStartAfter);
                        resolve(true);
                    } else {
                        console.log("마지막 페이지 입니다.");
                        resolve(false);
                    }
                });
            });
        },
};

state 설정

먼저 공통 데이터로 사용할 state는 orderListfbStartAfter입니다.
orderList는 리스트 데이터가 들어갈 배열변수입니다.
fbStartAfter는 2번째 이상 데이터를 불러올 때 Query에 작성할 StartAfter 정보를 저장합니다.

getters 설정

getters는 computed처럼 데이터의 실시간 변경 정보를 여러 곳에서 사용할 수 있게 해줍니다.
orderList는 state에 있는 리스트 에이터를 원하는 곳에 보여줍니다.
fbStartAfter도 state에 있는 fbStartAfter 정보를 보여 줍니다.

mutations 설정

mutations은 state의 상태를 변경할 때 사용하며 MUT_SET_ORDER_LISTorderList에 배열을 추가할 떄 사용합니다. MUT_SET_startAfterfbStartAfter의 값을 변경해 줍니다.

actions 설정

actions에 설정한 ACT_GET_ORDER_LIST는 데이터베이스(Firestore)에서 자료를 불러오는 역활을 합니다. 여기에서 핵심적인 역활을 수행 합니다.

Firestore는 자료를 페이지화 하기 위해 가장 처음 data를 불러오고 불러온 값의 마지막 정보를 활용하여 그 다음 페이지의 DATA를 불러올 때 시작점(startAfter)으로 활용하게 되어 있습니다.
다시말하면 2가지 형태의 Query로 데이터를 불러오게 하면 됩니다.

ACT_GET_ORDER_LIST({ state, commit }, getMode) {
    return new Promise((resolve) => {
        let query;
        if (getMode == "first") {
            query = DB.collection("orders").orderBy("date", "desc").limit(5);
        } else {
            query = DB.collection("orders").orderBy("date", "desc").startAfter(state.fbStartAfter).limit(5);
        }
        query.get().then((querySnapshot) => {
            if (querySnapshot.docs[0]) {
                const fbStartAfter = querySnapshot.docs[querySnapshot.docs.length - 1];
                const data = querySnapshot.docs.map((doc) => Object.assign({ id: doc.id }, doc.data()));
                commit("MUT_SET_ORDER_LIST", data);
                commit("MUT_SET_startAfter", fbStartAfter);
                resolve(true);
            } else {
                console.log("마지막 페이지 입니다.");
                resolve(false);
            }
        });
    });
},

이 함수를 사용 할 때 파라미터 값으로 getMode를 사용합니다. ACT_GET_ORDER_LIST(getMode)를 처음 사용할 때는 getMode의 초기값으로 설정한 "first"가 들어가 있으므로 query = DB.collection("orders").orderBy("date", "desc").limit(5);를 사용합니다.

이렇게 DATA를 불러오게 되면 data 변수에 불러온 DATA를 저장하고 MUT_SET_ORDER_LIST를 통해 state.orderList변수에 저장하여 프로젝트 전체에서 활용하게 됩니다.
또한 이렇게 불러 온 DATA 중 마지막 리스트의 정보를 fbStartAfter 변수에 저장하고 MUT_SET_startAfter를 통해 state.fbStartAfter변수에 저장하게 됩니다.

두번째 요청 부터는 getMode"first"가 아니기 때문에 쿼리는 query = DB.collection("orders").orderBy("date", "desc").startAfter(state.fbStartAfter).limit(5);를 사용하게 됩니다.
첫번째 요청과 다른 점은 .startAfter(state.fbStartAfter)를 사용한다는 점인데 이것은 이전 요청에서 획득한 마지막 페이지의 정보로부터 limit 갯수만큼 읽어 오겠다는 의미입니다. 여기서는 5개를 불러오겠다 입니다.

페이지 설정 (orderlist.vue)

vuex이므로 서버관련 정보는 위의 order.js에 함수 형태로 모두 작성해 놨고 이제는 각 Component에서 Vue-infinite-loading를 통해 사용하는 방법입니다.

아래는 전체코드입니다. 전체코드를 먼저 보여드리고 하나씩 설명하도록 하겠습니다.

<template>
    <div class="order-list-wrap">
        <div v-for="order in orderList" :key="order.id" class="order-list">
            {{order.title}}   
        </div>
        <infinite-loading @infinite="infiniteHandler" spinner="waveDots"></infinite-loading>
    </div>
</template>

<script>
import { mapActions, mapGetters } from "vuex";
import InfiniteLoading from "vue-infinite-loading";

export default {
    components: { InfiniteLoading },
     computed: {
        ...mapGetters([ "orderList", "fbStartAfter"]),
    },

    methods: {
        ...mapActions(["ACT_GET_TRANSACTION_LIST"]),
        infiniteHandler($state) {
            let getMode = this.fbStartAfter == null ? "first" : "next";
            this.ACT_GET_TRANSACTION_LIST(getMode).then((res) => {
                if (res) {
                    $state.loaded();
                } else {
                    $state.complete();
                }
            });
        },
    },
};
</script>

vue-infinite-loading 설정하기

먼저 해당 프로젝트에 vue-infinite-loading가 설치되어 있어야 합니다.

npm install vue-infinite-loading -s

또는

yarn add vue-infinite-loading

그리고 해당 Component에서 불러오고 선언을 합니다.

import InfiniteLoading from "vue-infinite-loading";
components: { InfiniteLoading }

html template에서 리스트의 가장 아래쪽에 <infinite-loading></infinite-loading>를 붙혀 넣습니다.

<template>
    <div class="order-list-wrap">
        <div v-for="order in orderList" :key="order.id" class="order-list">
            {{order.title}}   
        </div>
        <infinite-loading @infinite="infiniteHandler" spinner="waveDots"></infinite-loading>
    </div>
</template>

원리는 .order-list에 반복리스트가 아래로 쭈~~ㄱ 출력이 될 것입니다. 그리고 그 가장 아래쪽에 <infinite-loading></infinite-loading>가 있을 것이구요. 사람이 페이지를 스크롤해서 가장 아래쪽에 도달하게 되면 <infinite-loading></infinite-loading>가 화면에 노출이 될 것이고 그때 이벤트가 발생하여 infiniteHandler 함수를 호출하게 되는 식입니다.
그러면 infiniteHandler함수에서 서버의 데이터를 불러오게 되는 원리입니다.

이제 methods에 정의 되어 있는 infiniteHandler함수를 보도록 하겠습니다.

infiniteHandler($state) {
    let getMode = this.fbStartAfter == null ? "first" : "next";
    this.ACT_GET_TRANSACTION_LIST(getMode).then((res) => {
        if (res) {
            $state.loaded();
        } else {
            $state.complete();
        }
    });
},

$store에 있는 order.js에서 설정해 놨던 this.fbStartAfter의 초기값은 null입니다.
그래서 처음 infiniteHandler()가 실행되게 되면 getMode에는 "first"이 들어가게 됩니다.
그리고 $storeactions에 설정해 놨던 this.ACT_GET_TRANSACTION_LIST(getMode)를 실행하게 됩니다. 아까도 설명했듯이 getMode"first"이므로 startAfter가 없는 Query가 실행될 것입니다.
그러면서 자동으로 startAfter에 리스트의 마지막 정보가 들어갈 것이구요.
두번째 이후 부터는 this.fbStartAfternull이 아니기 때문에 getMode에는 "next"가 들어가게 됩니다.
그리고 Promise로 부터 피드백 받은 restrue인 경우는 리스트 정보가 더 있다는 뜻으로 $state.loaded();에게 알려주고 false인 경우는 $state.complete();에게 마지막 페이지라는 것을 알려주면 됩니다.

로딩중

UX 공작소

고급지게 만들어 저렴하게 배포는 공작소