Pinia master course
  • 🍏Introduction
    • Why Pinia
      • Introduction & Project setup
      • Vuex VS Pinia
  • 🍎Essential of Pinia
    • Create Store, State, Action, Getters
    • Use store in vue component
      • [Optional] 구조분해는 왜 reactivity를 잃게 만들까?
      • 비동기 액션 생성하기
      • 로컬스토리지 사용하기
Powered by GitBook
On this page
  • 컴포넌트: UserProfile.vue
  • API 사용하기
  • [Optional] api layer 작성하기
  • UserStore 작성하기
  • stores/user.js
  • UserProfile.vue 작성하기
  • 개별 스토리
  • 테스트 store 주입
  • 결과
  1. Essential of Pinia
  2. Use store in vue component

비동기 액션 생성하기

API를 호출해 store를 업데이트하는 실습을 해봅니다.

Previous[Optional] 구조분해는 왜 reactivity를 잃게 만들까?Next로컬스토리지 사용하기

Last updated 1 year ago

컴포넌트: UserProfile.vue

API 사용하기

dicebear의 API를 사용합니다.

pnpm add 
@dicebear/core
@dicebear/collection
axios

[Optional] api layer 작성하기

dicebear에서 제공하는 sdk를 사용할 것이기 때문에 axios를 직접 사용하지는 않습니다.

import axios from "axios";

export const httpInstance = axios.create({
  baseURL: "/api",
})

export const http =  {
  get: (url,params) => httpInstance.get(url, params),
  post: (url, params) => httpInstance.post(url, params)
}

UserStore 작성하기

stores/user.js

import { defineStore } from "pinia";
import { createAvatar } from '@dicebear/core';
import { 
  adventurer,
  adventurerNeutral,
  avataaars,
  avataaarsNeutral,
  bigEars,
  bigEarsNeutral,
  bigSmile,
  bottts,
  botttsNeutral,
  croodles,
  croodlesNeutral
 } from '@dicebear/collection';

const profiles = [
  adventurer,
  adventurerNeutral,
  avataaars,
  avataaarsNeutral,
  bigEars,
  bigEarsNeutral,
  bigSmile,
  bottts,
  botttsNeutral,
  croodles,
  croodlesNeutral
];

import 구문 부터 살펴보면 dicebear에서 제공하는 여러 스타일을 가져오기 위해 여러 모듈들을 임포트합니다.

이어서 작성합니다.

const getRandomUserData = async (userId) => {
  const avatar = await createAvatar(profiles[Math.floor(profiles.length * Math.random())])
  return {
    name: `Dante${userId}`,
    age: Math.floor(Math.random() * 100),
    userId,
    avatar: await avatar.toDataUri()
  }
};

userId가 입력되면 임의의 유저 데이터를 받아오기 위한 헬퍼 함수를 작성합니다.

이어서 작성합니다.


export const useUserStore = defineStore("user", {
  state:() => ({
    userData: null,
    loading: false,
    error: null
  }),
  getters: {
    isUserLoggedIn: state => !!state.userData,
  },
  actions: {
    async fetchUserData(userId) {
      this.loading = true;

      const response = await new Promise(resolve => {
        setTimeout(() => {
          resolve(getRandomUserData(userId))
        },700)
      });
      this.userData = response;
      this.loading = false;
    },
  }
})

액션에 비동기함수 fetchUserData를 선언합니다. 로딩 시간을 길게 주어 loading state가 올바르게 표기되는지 확인하기 위해 Promise 내부에 임의로 700ms 의 대기시간을 넣었습니다.

UserProfile.vue 작성하기

<template>
  <div class="flex items-center p-2 bg-white rounded-lg shadow-sm hover:bg-gray-100">
    <img 
      v-if="userData && userData.avatar"
      :src="userData.avatar" 
      alt="User Avatar" 
      class="w-8 h-8 rounded-full"
    />
    <span v-if="userData" class="ml-3 text-sm font-medium text-gray-700">
      {{ userData.name }}
    </span>
    <span v-if="userData.loading" class="ml-3 text-sm font-medium text-gray-700">
      Loading...
    </span>
  </div>
</template>

<script setup>
import { storeToRefs } from 'pinia';
import { useUserStore } from '../../stores/user'
const userStore = useUserStore();
const { userData } = storeToRefs(userStore);
</script>

테일윈드를 이용해 스타일링을 합니다.

스토어 선언할 때 작성한 useUserStore를 호출해 userStore를 가져옵니다.

storeToRefs를 이용해 userData를 구조분해하여 userStore.userData와 같이 작성하지 않아도 reactivity를 유지하게 합니다.

template 내부에 v-if 디렉티브 사용한 부분만 간단히 보면 userData가 없는 동안에는 loading 문구를 표시합니다.

<span v-if="userData" class="ml-3 text-sm font-medium text-gray-700">
  {{ userData.name }}
</span>
<span v-if="userData.loading" class="ml-3 text-sm font-medium text-gray-700">
  Loading...
</span>

이 컴포넌트는 userData를 내부에서 fetch하지 않고 데이터를 보여주는 presentation만 담당합니다.

이제 작성한 컴포넌트를 테스트하기 위해 스토리북 코드를 작성하겠습니다. UserProfile.vue와 동일한 경로에 UserProfile.stories.js를 생성합니다.

import UserProfile from './UserProfile.vue';
import { useUserStore } from '@/stores/user';

export const Base = {
  render: (args) => ({
    components: { UserProfile },
    setup() {
      const userStore = useUserStore();
      userStore.userData = {
        name: "John Doe",
        email: "john@example.com",
        id: 53,
        avatar: "https://api.dicebear.com/7.x/fun-emoji/svg"
      }
      return { args }
    },
    template: /* html */`
      <UserProfile 
        v-bind="args"
      />
    `,
  }),
  args: {
    alt: "storybook Profile"
  }
}

export default {
  component: UserProfile
}

일부분씩 뜯어서 보겠습니다.

최하단의 export default

export default {
  component: UserProfile
}

UserProfile 스토리가 생긴것을 볼 수 있습니다.

개별 스토리

export const Base = {...

UserProfile 내부에 Base story를 생성합니다.

테스트 store 주입

setup() {
      const userStore = useUserStore();
      userStore.userData = {
        name: "John Doe",
        email: "john@example.com",
        id: 53,
        avatar: "https://api.dicebear.com/7.x/fun-emoji/svg"
      }
      return { args }
    },
template: /* html */`
  <UserProfile 
    v-bind="args"
  />
`,

userStore.userData = { 를 사용해 테스트에 사용할 스토어를 초기화 합니다. 모킹 데이터를 넣어준 것입니다.

결과

🍎
default
hover
https://www.dicebear.com/styles/
UserProfile component