2023년 2월 6일

코딩 - Vue 3 + Typescript + Vite + Vuetify 3.x 로 시작하는 웹 프로그램 : 반복작업

특정 화면에서 반복적인 작업을 수행해야하는 아래와 같은 경우를 가정해보자. 

  • Index.vue 는 15초를 주기로  새로운 배경 이미지를 보여 준다.

개발은 크게 고민하지 않고 공개된 Unsplash  API 을 활용 이미지 목록을 가져와서 주기적으로 화면의 배경 이미지를 바꿔주었다. 

  • src/store/unsplash.js 
  • src/view/Indes.vue

Index View 에서는 ① mounted 되면  upsplash 에서 가져온 이미지 목록이 있는지 확인하고 없는 경우 fetch 함수를 호출하여 이미지 목록을 가져온다. ② setTimeout 함수를 사용하여 15000 단위로 바탕하면 이미지를 바꿔준다.

// Utilities
import { defineStore } from 'pinia'
// Install unsplash
import { createApi } from 'unsplash-js'
//import whatwgFetch from "whatwg-fetch";
const unsplash = createApi({
accessKey: import.meta.env.VITE_UNSPLASH_API_KEY,
//fetch: whatwgFetch,
})
export const useUnsplashStore = defineStore({
// id is required so that Pinia can connect the store to the devtools
id: 'unsplash',
state: () => ({
photos: [],
isLoaded: false,
}),
getters: {
total: state => state.photos.length,
},
actions: {
getRandomPhoto () {
return this.photos[Math.floor(Math.random() * this.photos.length)]
},
async fetch (queryString) {
if (this.isLoaded) return
queryString = queryString || 'dark,girls,sexy'
await unsplash.photos
.getRandom({ query: queryString, count: 50 })
.then(result => {
if (result.type === 'success') {
this.photos = result.response
this.isLoaded = true
}
})
},
},
})
view raw unsplash.ts hosted with ❤ by GitHub
<template>
<v-app>
<NavbarDefault></NavbarDefault>
<v-main>
<v-parallax height="100vh" :style="headerStyle">
</v-parallax>
<div class="mr-auto ml-10 mb-50" style="position:absolute; bottom:150px; font-size: .8rem;">
<v-card color="transparent" theme="dark" max-width="450" v-if="bgPhoto.visible" flat>
<v-card-text class="text-h7 py-2">{{ bgPhoto.unsplash.description || bgPhoto.unsplash.alt_description }}</v-card-text>
<v-card-actions>
<v-list-item class="w-100">
<template v-slot:prepend>
<v-avatar color="grey-darken-3">
<v-img :src="bgPhoto.unsplash.user.profile_image.small" alt="{{ bgPhoto.unsplash.user.username }}"></v-img>
</v-avatar>
</template>
<v-list-item-title style="font-size: .8rem;">{{ bgPhoto.unsplash.user.name }}</v-list-item-title>
<v-list-item-subtitle><small>{{ bgPhoto.unsplash.location.name }}</small></v-list-item-subtitle>
<template v-slot:append>
<div class="justify-self-end ml-5">
<v-icon class="me-1" icon="mdi-thumb-up"></v-icon>
<span class="subheading me-2">{{ bgPhoto.unsplash.likes }}</span>
<span class="me-1">·</span>
<v-icon class="me-1" icon="mdi-account-eye"></v-icon>
<span class="subheading">{{ bgPhoto.unsplash.views }}</span>
</div>
</template>
</v-list-item>
</v-card-actions>
</v-card>
</div>
</v-main>
<FooterDefault></FooterDefault>
</v-app>
</template>
<script setup lagn="ts">
// Composables
import NavbarDefault from "../layouts/navbars/NavbarDefault.vue";
import FooterDefault from "../layouts/footers/FooterDefault.vue";
// Utilities
import { useUnsplashStore } from "@/store/unsplash";
import {
onMounted,
computed,
ref,
reactive
} from "vue";
// Globals
const unsplash = useUnsplashStore();
const bgPhoto = reactive({
url: "https://cdn.vuetifyjs.com/images/backgrounds/vbanner.jpg",
visible: false,
unsplash: null,
});
const headerStyle = computed(() => {
return {
backgroundImage: `url(${bgPhoto.url})`,
transition: "background 1000ms ease-in 500ms",
backgroundSize: "cover",
};
})
onMounted(async () => {
if (!unsplash.isLoaded) {
await unsplash.fetch();
}
bgUpdateFromUnsplash();
});
function bgUpdateFromUnsplash() {
if (unsplash.total > 0) {
setTimeout(function () {
var proxyImage = new Image();
let unsplashPhoto = unsplash.getRandomPhoto();
proxyImage.src = unsplashPhoto.urls.regular;
proxyImage.onload = function () {
bgPhoto.url = proxyImage.src;
bgPhoto.unsplash = unsplashPhoto;
bgPhoto.visible = true;
bgUpdateFromUnsplash();
};
}, 15000);
}
}
</script>
view raw Index.vue hosted with ❤ by GitHub

위 코드의 문제점은 아래와 같다.

  1. 다른 View 로 이동하더라도 백그라우드에서 ② 작업이 무한 실행된다. 
  2. 다른 View 에서 Index View 로 이동하면 ① 작업을 수행하고 ② 작업을 다시 무한 실행한다.
  3.  불필요한 메모리 누수가 발생한다. 


◼︎ 개발환경 

  • Model : MacBook Pro (14-inch, 2021)
  • CPU : Apple M1 Pro 
  • MENORY : 16GB
  • DISK : 512 GB SSD
  • OS : macOS 13.2 (22D49)

문제 해결

Vue3 Lifecycle 에 따르면 Composition API 에서 setup 은 create 와 같으며 View 가 마운트 되면  (View 가 화면에 보여지면) mounted (onMounted) 가 실행되고 언마우트 되면 (다른 View 로 이동하면) unmounted (onUnmounted)가 실행되는데 이점을 이용하면 Index 가 화면에 보여질 때만 ② 작업을 수행하도록 할 수 있다. 

반복작업은  setInterval() 함수를 사용하고 더이상 반복작업이 필요없는 경우에는 (다른 View 로 이동할 때) , setInterval() 호출시 리턴되는 ID 값을 인자로 clearInterval() 함수를 호출하면 timer 을 초기활 할 수 있다. 

<template>
<v-parallax :style="headerStyle" class="h-screen w-auto" />
<div class="mr-auto ml-10 mb-50" style="position:absolute; bottom:150px; font-size: .8rem;">
<v-card v-if="bgPhoto.visible" color="transparent" theme="dark" max-width="450" flat>
<v-card-text class="text-h7 py-2">{{
bgPhoto.unsplash.description ||
bgPhoto.unsplash.alt_description
}}</v-card-text>
<v-card-actions>
<v-list-item class="w-100">
<template #prepend>
<v-avatar color="grey-darken-3">
<v-img :src="bgPhoto.unsplash.user.profile_image.small" alt="{{ bgPhoto.unsplash.user.username }}" />
</v-avatar>
</template>
<v-list-item-title style="font-size: .8rem;">{{
bgPhoto.unsplash.user.name
}}</v-list-item-title>
<v-list-item-subtitle><small>{{
bgPhoto.unsplash.location.name
}}</small></v-list-item-subtitle>
<template #append>
<div class="justify-self-end ml-5">
<v-icon class="me-1" icon="mdi-thumb-up" />
<span class="subheading me-2">{{ bgPhoto.unsplash.likes }}</span>
<span class="me-1">·</span>
<v-icon class="me-1" icon="mdi-account-eye" />
<span class="subheading">{{ bgPhoto.unsplash.views }}</span>
</div>
</template>
</v-list-item>
</v-card-actions>
</v-card>
</div>
</template>
<script setup lang="ts">
// Utilities
import { useUnsplashStore } from '@/store/unsplash'
import {
computed,
onMounted,
onUnmounted,
reactive,
} from 'vue'
// Globals
const unsplash = useUnsplashStore()
const bgPhoto = reactive({
url: 'https://cdn.vuetifyjs.com/images/backgrounds/vbanner.jpg',
visible: false,
unsplash: null,
})
const headerStyle = computed(() => {
return {
backgroundImage: `url(${bgPhoto.url})`,
transition: 'background 1000ms ease-in 500ms',
backgroundSize: 'cover',
}
})
const intervalTime: number = 15000
let intervalId: number
onMounted(async () => {
if (!unsplash.isLoaded) {
await unsplash.fetch()
}
updateBgImage()
intervalId = setInterval(updateBgImage, intervalTime)
})
onUnmounted(() => {
clearInterval(intervalId)
})
function updateBgImage() {
if (unsplash.total > 0) {
const proxyImage = new Image()
const unsplashPhoto = unsplash.getRandomPhoto()
proxyImage.src = unsplashPhoto.urls.regular
proxyImage.onload = function () {
bgPhoto.url = proxyImage.src
bgPhoto.unsplash = unsplashPhoto
bgPhoto.visible = true
}
}
}
</script>
view raw Index.vue hosted with ❤ by GitHub


참고자료

댓글 없음:

댓글 쓰기