Pinia를 이용해보자

김규연·2024년 8월 22일

Vue

목록 보기
5/7

props,emits을 사용하는 경우도 있지만 vue3에는 pinia라는 상태관리 라이브러리가 존재한다. 이번 글에서는 pinia를 통해 전에 블로그에서 작성했던 코드를 변경해 보겠다.

📌 pinia는 다음과 같은 상황에서 주로 사용된다.

  • 전역 상태 관리 : 여러 컴포넌트에서 공유해야하는 데이터를 관리할 때
  • 컴포넌트 간 통신 : 직접적인 관계가 없는 컴포넌트 간에 데이터를 주고받을 때
  • 서버 상태 동기화 : 서버에서 받아온 데이터를 여러 컴포넌트에서 일관되게 사용해야 할 때
  • 복잡한 애플리케이션 로직 분리 : 비즈니스 로직을 컴포넌트에서 분리하여 관리할 때

📌 Pinia의 주요 특징

  • 간단한 설정 : vuex보다 설정이 간단하다.
  • TypeScript 지원 : 타입 추론이 잘 동작하여 TypeScript와 함께 사용하기 좋다.
  • 모듈화 : 여러 개의 스토어를 쉽게 만들고 관리할 수 있다.
  • 가벼움 : 기본적으로 가볍고, tree-shaking이 가능하다.
  • Vue DevTools 지원 : Vue DevTools 와 통합되어 디버깅이 쉽다.

먼저 postsStore.ts를 생성하여 Pinia store를 만든다.

import { defineStore } from 'pinia';
import { getPosts } from '../apicontroller/posts';
import { PostData, PostDto } from '../assets/interfaces/index';
import { menuList } from '../assets/column/index';

export const usePostsStore = defineStore('posts', {
  state: () => ({
    currentPage: 1,
    rowsPerPage: 20,
    totalPages: 0,
    posts: [] as PostData[],
    currentRoute: '',
  }),
  getters: {
    tableTitle: (state) => {
      const menuItem = menuList.find((item) => item.route === state.currentRoute);
      return menuItem ? menuItem.label : '';
    },
  },
  actions: {
    async fetchPosts() {
      try {
        const response = await getPosts(
          this.currentPage,
          this.rowsPerPage,
          this.currentRoute || '/posts'
        );
        this.posts = response.items.map((item: PostDto) => ({
          postId: item.post_id,
          postTitle: item.post_title,
          writer: item.post_username,
          department: item.mem_address1,
          postDatetime: item.post_datetime,
          postHit: item.post_hit,
        }));
        this.totalPages = response.totalPages;
      } catch (error) {
        console.error('게시물 가져오기 실패: ', error);
      }
    },
    setCurrentPage(page: number) {
      this.currentPage = page;
      this.fetchPosts();
    },
    setCurrentRoute(route: string) {
      this.currentRoute = route;
      this.fetchPosts();
    },
  },
});
  • useUserStore를 정의하여 사용자 관련 상태와 로직을 관리한다.
  • state는 저장할 데이터를 정의한다.
  • getters는 상태를 기반으로 계산된 값을 반환한다.
  • actions는 상태를 변경하는 메서드들이다.
  • 컴포넌트에서 useUserStore()를 호출하여 스토어를 사용한다.
  • 컴포넌트 템플릿에서 스토어의 상태와 게터를 직접 사용할 수 있다.

다음 PostsComponent.vue를 고쳤다.

<template>
  <div class="q-pa-md">
    <q-table
      flat
      bordered
      :title="store.tableTitle"
      :rows="store.posts"
      :columns="postColumn"
      :rows-per-page-options="[store.rowsPerPage]"
      row-key="postId"
      hide-bottom
    />
  </div>
</template>

<script setup lang="ts">
import { onMounted, watch } from 'vue';
import { usePostsStore } from '../stores/postsStore';
import { postColumn } from '../assets/column/index';

const props = defineProps<{
  routeInfo: string;
}>();

const store = usePostsStore();

watch(() => props.routeInfo, (newRoute) => {
  store.setCurrentRoute(newRoute);
});

onMounted(() => {
  store.setCurrentRoute(props.routeInfo);
});
</script>

PageComponent.vue

<template>
  <div class="q-pa-lg flex flex-center">
    <q-pagination
      v-model="currentPage"
      :max="store.totalPages"
      :max-pages="9"
      :ellipses="false"
      :boundary-numbers="false"
      direction-links
      boundary-links
      icon-first="skip_previous"
      icon-last="skip_next"
      icon-prev="fast_rewind"
      icon-next="fast_forward"
    />
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import { usePostsStore } from '../stores/postsStore';

const store = usePostsStore();

const currentPage = computed({
  get: () => store.currentPage,
  set: (value) => store.setCurrentPage(value),
});
</script>

PostsPage.vue

<template>
  <q-page class="column items-center justify-start">
    <div class="col-12 q-pa-md">
      <posts-component
        :route-info="currentRoute"
      ></posts-component>
    </div>
    <div class="col-12 q-pa-md">
      <page-component></page-component>
    </div>
  </q-page>
</template>

<script setup lang="ts">
import { computed, watch } from 'vue';
import { useRoute } from 'vue-router';
import { usePostsStore } from '../stores/postsStore';
import PostsComponent from 'components/PostsComponent.vue';
import PageComponent from 'components/PageComponent.vue';

const route = useRoute();
const store = usePostsStore();

const currentRoute = computed(() => route.fullPath);

watch(currentRoute, (newRoute) => {
  store.setCurrentRoute(newRoute);
});
</script>

코드를 보면 props, emits을 사용했을 때보다 코다 훨씬 간결해 진걸 확인할 수 있다.

📌 defineProps와 defineEmits 사용할 떄 장단점

  • 장점
    • 컴포넌트 간 관계가 명확하고, 부모-자식 관계를 통해 데이터 흐름을 쉽게 파악할 수 있다.
    • 컴포넌트의 재사용성이 높아진다. props를 통해 외부에서 데이터를 주입받기 때문에 다양한 상황에서 재사용하기 쉽다.
    • 컴포넌트의 독립성이 보장된다. 외부 의존성 없이 자체적으로 동작할 수 있다.
    • 소규모의 애플리케이션에서는 구현이 간단하고 직관적이다다.
    • Vue의 기본 기능을 사용하므로 추가적인 라이브러리 설치가 필요 없다.
  • 단점
    • 깊은 컴포넌트 트리에서는 props drilling 문제가 발생할 수 있다. 여러 계층을 거쳐 데이터를 전달해야 할 때 코드가 복잡해질 수 있다.
    • 전역 상태 관리가 어렵다. 여러 컴포넌트에서 공유해야 하는 데이터를 관리하기 어려울 수 있다.
    • 상태 변경 추적이 어려울 수 있다. 특히 큰 애플리케이션에서 여러 컴포넌트가 상태를 변경할 때 디버깅이 복잡해질 수 있다.

📌 Pinia 사용할 때 장단점

  • 장점
    • 중앙 집중식 상태 관리가 가능하다. 애플리케이션의 전역 상태를 효과적으로 관리할 수 있다.
    • props drilling 문제를 해결할 수 있다. 깊은 컴포넌트 트리에서도 상태에 쉽게 접근할 수 있다.
    • 상태 변경의 추적과 디버깅이 용이하다. 개발자 도구를 통해 상태 변화를 쉽게 모니터링할 수 있다.
    • 비동기 작업 처리가 편리하다. actions를 통해 복잡한 비동기 로직을 관리할 수 있다.
    • 타입스크립트와의 통합이 우수하다. 타입 안정성을 보장받을 수 있다.
    • 모듈화가 용이하다. 기능별로 store를 분리하여 관리할 수 있다.
  • 단점
    • 작은 규모의 애플리케이션에서는 과도한 설정일 수 있다. 단순한 상태 관리에 비해 초기 설정이 복잡할 수 있다.
    • 컴포넌트의 재사용성이 떨어질 수 있다. store에 의존적인 컴포넌트는 다른 환경에서 재사용하기 어려울 수 있다.
    • 외부 라이브러리 의존성이 생긴다. Pinia를 설치하고 관리해야 한다.

📖 결론

  • 작은 규모라면 props/emits, 큰 규모라면 Pinia가 유리할 수 있다.
  • 간단한 상태라면 props/emits, 복잡한 상태 관리가 필요하다면 Pinia가 좋다.
  • 앱이 커질 가능성이 있다면 Pinia를 고려해볼 만하다.
  • 대규모 상태 관리가 필요한 경우 Pinia가 성능상 이점을 줄 수 있다.
profile
오늘도 뚠뚠 개미 개발자

0개의 댓글