검색

레이블이 Pinia인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Pinia인 게시물을 표시합니다. 모든 게시물 표시

2023년 3월 8일

코딩 - Vue 3 + Typescript + Vite + Vuetify 3.x 로 시작하는 웹 프로그램 : 사용자 정의 콤포넌트

컴포넌트를 사용하면 UI를 독립적이고 재사용 가능한 조각으로 분할하고 각 조각을 독립적으로 생각할 수 있다. 일반적으로 앱은 중첩된 컴포넌트의 트리로 구성된다.

사용자 정의 콤포넌트를 작성할떄 가장 고민되는 것이 부모 콤포넌트와 자식 콤포넌트 사이에 정보 교환인데 보통 아래 규칙을 따른다.

❶ 부모 > 컴포넌트 : Props 를 통하여 전달한다.
❷ 컴포넌트 > 부모 : 이벤트를 발생 시켜 데이터를 전달한다.
❸ 전역 : 전체 프로그램에서 공유가 필요한 데이터의 경우는 pinia Store 를 사용한다.

다음 예시는 이미지 목록을 사용자 정의 컴포넌트 사용하여 구현한 예이다.
  • /views/Photos.vue : 이미지 목록.
  • /components/PhotoCard.vue : 목록에 보여지는 사용자 정의 컴포넌트.
  • /store/photos.store.ts : 이미지 데이터를 위한 Store.

❶ Props 

 v-bind  를 사용해서 부모의 값을 props 에 바인딩 할 수 있다. 부모의 값이 변경되면 동적으로 하위(자식 컴포넌트)  값도 변경된다.  
 
<PhotoCard v-bind:image="image" @imageEditEvent="edit" @imageViewEvent="view"></PhotoCard>

 v-bind  를 단축하여 아래와 같이 사용할 수 도 있다.

<PhotoCard :image="image" @imageEditEvent="edit" @imageViewEvent="view"></PhotoCard>

props 는 단반향 바인딩 만을 지원하는데, 이는 자식 컴포넌트가 실수로 부모 콤포넌트의 값을 변경하는 것을 막기 위함이다.  props 값을 변경해야 할 필요가 있는 경우 ⑴ 자식 콤포넌트는 초기 값으로 만 사용하고 로컬 값을 생성하여 사용하거나 ⑵ prop 값으로 부터 계산되는 값을 사용하는 것이다. 

⑴ prop 값으로 로컬 값을 초기화하여 사용

const props = defineProps({
image: Object,
})
const localImage = ref(props.image)

⑵ prop 값으로 부터 계산되는 값을 사용

const props = defineProps({ attachment: Object })
const emit = defineEmits(['previewEvent'])
const thumbnailUrl = computed(() => {
return attachments.isAudio(props.attachment) ? attachments.thumbnails.AUDIO :
attachments.getAttachmentUrl(props.attachment,
{ thumbnail: true, width: 300, height: 300 });
})
 
 

❷ 커스텀 이벤트 

커스텀 이벤트를 사용하면 자식 컴포넌트에서 부모에게 값을 전달 할 수 있다. 

자식 컴포넌트 
<template>
<v-btn size="small" color="white" variant="text" icon="mdi-eye"
@click="emit('imageViewEvent', image)"></v-btn>
</template>
<script setup lang="ts">
defineProps({
image: Object,
})
const emit = defineEmits(['imageEditEvent', 'imageViewEvent'])
</script>

부모 컴포넌트
<PhotoCard :image="image" @imageEditEvent="edit" @imageViewEvent="view"></PhotoCard>

⓷ Pinia Store 

Vuex 와 다르게 Pinia 를 라이브러리를 사용하면 아주 쉽게 저장소를 생성하고 전역으로 공유할 수 있다. 
이미지 목록을 가져오는 Photos.vue 컴포넌트에서 fetch 함수를 사용하여 데이터를 가져온다. Store 의 상태는 전역으로 공유가 되기 때문에 다른 뷰로 이동하고 다시 돌아오는 경우에도 Store 가 가지고 있는 정보는 유지되어 사용할 수 있다. 

onMounted 함수 내에서 store에서 정보를 가져오는 코드를 포함하여 구현하는 경우 불필요한 서버 통신을 최소화할 수 있어 편리하게 사용할 수 있다.

import { usePhotosStore } from '@/store/photos.store'
const photos = usePhotosStore()
const images = ref([])
onMounted(async () => {
if (!photos.isLoaded) {
overlay.value = true
photos.setPage(1)
await photos.fetch()
overlay.value = false
}
images.value = photos.photos
})

참고자료

  1. How to use Watch in Vue 3 in Composition API
  2. https://vuejs.org/guide/essentials/watchers.html#basic-example
  3. https://www.zhenghao.io/posts/verify-image-url
  4. 12 VueJS Best Practices for Pro Developers
  5. How To Pass Data Between Components In Vue.js

2022년 9월 20일

코딩 - Vue 2 환경에서 Vuex 를 pinia 로 변경하기

Vue3 에서 공식 상태 관리 라이브러리가 Vuex 에서 Pinia 로 변경되었다. Vue2, Vue3 모두를 지원하고 있고 향후 Vue3 환경으로 전환을 고려하여 Vuex 을 pinia 로 변경하는 작업을 진행하였다.

Install Pinia 

설치는 Pinia 공식 사이트의 가이드에 따라 진행하였다. 

❶ 패키지 관리자를 사용하여 pinia 설치

npm install --save pinia

❷ App 에서 플러그인을 설치하고 App 루트에 생성된 pinia 객체를 삽입



여러가지 문제들과 조우

공식 가이드에 따라 설치를 진행하였지만 여러가지 여러 오류가 발생되었으며 가장 먼저 아래 컴파일 오류를 접하게 된다.

문제 ① - 터미널에서 확인

Can't import the named export 'computed' from non EcmaScript module (only default export is available)

Webpack bundler .mjs 파일을 해석하지만 그것이 ESM 모듈이라는 것을 알지 못한다는 것을 의미한다.  이문제는 vue.config.js  파일에 webpack 규칙에 추가하여 해결 할 수 있다. 

module.exports = {
configureWebpack: {
module: {
rules: [
{
test: /\.mjs$/,
include: /node_modules/,
type: "javascript/auto"
}
]
}
},


문제 ② - 터미널에서 확인

Uncaught Error: [🍍]: getActivePinia was called with no active Pinia. Did you forget to install pinia? const pinia = createPinia() app.use(pinia) This will fail in production.

이문제는 defineStore 함수를 사용하여 정의된 Store 객체를 다른 컴포넌트에서 사용하려고 하는 경우 발생하는 것으로 확인되었다. 이 문제는 Pinia가 준비되기 전에 다른 컴포넌트에서  정의된 Store 객체를  사용하는 경우에 발생된다고 하는데 여러가지 방법을 사용해보았으나 해결되지 않았고 pinia 객체를 인자로 하여 use...Store() 함수를 호출하는 방법으로 해결할 수 있었다. 

import { useStreamStore } from '@/store/streamStore'
import { pinia } from "@/store";
const streamStore = useStreamStore(pinia);

문제 ③ - Chrome 개발자 모드에서 확인

  pinia.mjs?be92:926 Uncaught TypeError: Cannot read properties of undefined (reading 'startsWith') at devtoolsPlugin (pinia.mjs?be92:926:1)


Chrome 개발자 도구에서는 확인되지 않았지만 Firefox 에서 Vue Devtools 를 설치하고 확인한 결과 원인을 확인 할 수 있었다.



이 문제는 Store 을 정의할 때 id 값을 추가하여 해결할 수 있었다. 아래는 관련 소스이다.


사용후기 

pinia 를 적용하고 2주 정도 사용을 해보니 여러 장점들이 보이기 시작했다. vuex 경우는 하나의 저장소를 생성하면 state, getters, mutations, actions, modules  속성을 필요에 따라 정의해야 하는데 여간 불편한 것이 아니어서 멀리하는 경향이 있었다.(로그인의 경우만 적용)  pinia 의 경우 이부분이 단순화 되어 있어 적용이 쉬웠다. 또한 자꾸 사용을 하다 보니 왜 Vue 에서 store, service 로 모듈화시켜 개발하는지 조금 이해가 되었다.  (다수의 vue 오픈소스를 보면 store, service 로 모듈화 되어 있었다.)

참고자료