λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
[개발] Practice/Vue.js

[Vue.js] mitt 을 μ‚¬μš©ν•˜μ—¬ 멀리 떨어진 Component κ°„ 데이터 μ „μ†‘ν•˜κΈ°

by Connecting-the-dots 2022. 4. 7.
728x90
λ°˜μ‘ν˜•

πŸ’‘ μ‹€μŠ΅ 포인트!

  • 이전에 ν•„ν„° μ„ νƒν•˜λ©΄ 적용이 되게 ν•˜λŠ” κΈ°λŠ₯을 κΉœλΉ‘ν•˜κ³  λ§Œλ“€μ§€ μ•Šμ•˜λ‹€κ³  ν–ˆλŠ”λ° 마침... 였늘 κ³΅λΆ€ν•΄μ„œ μ μš©κΉŒμ§€ ν•΄λ³΄μ•˜λ‹€.
  • 처음 κΈ°λŠ₯ λ§Œλ“€κ³  μ—¬λŸ¬λ²ˆ μ‹€ν–‰ν•΄λ³΄λ‹ˆ, ν•„ν„° 선택 νŽ˜μ΄μ§€μ—μ„œ 아직 ν•„ν„°λ₯Ό μ„ νƒν•˜μ§€λ„ μ•˜μ•˜λŠ”λ° 이전에 μ„ νƒν–ˆλ˜ ν•„ν„°κ°€ 기본으둜 μ„ νƒλœ 것을 보고 μ½”λ“œλ₯Ό μΆ”κ°€λ‘œ μˆ˜μ •ν•΄μ£Όμ—ˆλ‹€.
  • μ΄λ²ˆμ—λŠ” μΊ‘μ²˜ν•˜λ €κ³  μ—¬λŸ¬λ²ˆ μ‹€ν–‰ν•΄λ³΄λ‹ˆ, ν•„ν„°κ°€ μ„ νƒλœ μƒνƒœμ—μ„œ ν•„ν„° 선택 νŽ˜μ΄μ§€λ‚˜ κΈ€ μž‘μ„± νŽ˜μ΄μ§€μ—μ„œ "Cancel" λ²„νŠΌμ„ λˆŒλŸ¬λ„ λ‹€μŒλ²ˆ 이미지 μ—…λ‘œλ“œν•  λ•Œ ν•„ν„°κ°€ μ„ νƒλ˜μ–΄ μžˆμ–΄ μ½”λ“œλ₯Ό μΆ”κ°€ μˆ˜μ •ν•΄μ£Όμ—ˆλ‹€.

πŸ’œ mitt 라이브러리 μ‚¬μš©ν•˜κΈ°

  • 가끔 멀리 λ–¨μ–΄μ ΈμžˆλŠ” Component 둜 데이터 전솑을 ν•΄μ•Ό ν•  κ²½μš°κ°€ 생긴닀.
  • 예λ₯Ό λ“€μ–΄, λ‚˜μ˜ 경우 App.vue > Container.vue > FilterBox.vue 순으둜 Component 포함관계가 μžˆμ„ λ•Œ FilterBox.vue μ—μ„œ App.vue 둜의 데이터 전솑이 ν•„μš”ν–ˆλ‹€. 
  • 이런 μƒν™©μ—μ„œ custom event 둜 데이터 전솑을 ν•΄μ£ΌκΈ°μ—λŠ” 2번의 데이터 전솑 κ³Όμ •(FilterBox -> Container, Container -> App)을 거쳐야 ν•˜λ―€λ‘œ μ½”λ“œκ°€ 길어지고 λ³΅μž‘ν•΄μ§„λ‹€.
  • κ·ΈλŸ¬λ―€λ‘œ, mitt λΌλŠ” μ™ΈλΆ€ 라이브러리λ₯Ό μ‚¬μš©ν•˜λ©΄ 보닀 μ‰½κ²Œ 데이터 전솑이 κ°€λŠ₯ν•˜λ‹€.
    (Vue 2 λ²„μ „μ—μ„œλŠ” EventBus λΌλŠ” 것을 μ‚¬μš©ν•˜λ©΄ λ˜μ§€λ§Œ Vue 3 μ—μ„œλŠ” μ‚¬μš©μ΄ 아직 λΆˆκ°€ν•˜λ―€λ‘œ μ™ΈλΆ€ 라이브러리λ₯Ό μ„€μΉ˜ν•˜λŠ” 것!)

🀍 mitt μ„€μΉ˜ν•˜κΈ°

// npm 을 μ΄μš©ν•˜λŠ” 경우
npm install mitt

// yarn 을 μ΄μš©ν•˜λŠ” 경우
yarn add mitt
  • 터미널에 μœ„ 두 μ½”λ“œ 쀑 ν•˜λ‚˜λ₯Ό μž…λ ₯ν•˜μ—¬ mitt 라이브러리λ₯Ό μ„€μΉ˜ν•˜λ©΄ λœλ‹€.

🀍 mitt μ…‹νŒ…ν•˜κΈ°

import { createApp } from 'vue'
import App from './App.vue'

// mitt 라이브러리 μ…‹νŒ…
import mitt from 'mitt'
let emitter = mitt();
let app = createApp(App);
app.config.globalProperties.emitter = emitter;

app.mount('#app')
  • mitt μ…‹νŒ…μ€ μœ„μ™€ 같이 main.js 파일의 μ½”λ“œλ₯Ό μΆ”κ°€ 및 μˆ˜μ •ν•΄μ£Όλ©΄ λœλ‹€.
  • 참고둜 app.config.globalProperties λŠ” λͺ¨λ“  Component μ—μ„œ μ‚¬μš©ν•  수 μžˆλŠ” λ³€μˆ˜λ₯Ό 등둝할 수 있게 λ„μ™€μ£ΌλŠ” μΌμ’…μ˜ object 자료인데, μ‰½κ²Œ κΈ€λ‘œλ²Œ λ³€μˆ˜ 보관함이라고 μƒκ°ν•˜λ©΄ λœλ‹€.
  • μ΄λŸ°μ‹μœΌλ‘œ 자주 μ“°λŠ” axios 같은 것도 λ³΄κ΄€ν•˜λ©΄, 각 vue νŒŒμΌμ—μ„œ axios λ₯Ό import ν•΄μ˜€μ§€ μ•Šλ”λΌλ„ axios λ₯Ό μ‚¬μš©ν•  수 μžˆμœΌλ―€λ‘œ νŽΈλ¦¬ν•˜λ‹€.
    (λ‹€λ§Œ, 기쑴에 axios.get κ³Ό 같이 μ½”λ“œλ₯Ό μž‘μ„±ν–ˆλ‹€λ©΄, 이 방식을 μ‚¬μš©ν•  λ•Œμ—λŠ” this.axios.get κ³Ό 같이 μ½”λ“œλ₯Ό μž‘μ„±ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€.)

🀍 mitt μ‚¬μš©ν•˜κΈ°

this.emitter.emit('전솑할_λ•Œ_μ‚¬μš©ν• _이름', '전솑할_데이터')
  • 데이터λ₯Ό μ „μ†‘ν•˜κ³ μž ν•˜λŠ” κ³³μ—μ„œ μœ„μ™€ 같이 μ½”λ“œλ₯Ό μž‘μ„±ν•˜λ©΄ 전솑이 κ°€λŠ₯ν•˜λ‹€.

 

this.emitter.on('전솑할_λ•Œ_μ‚¬μš©ν•œ_이름', (data)=>{
  // 데이터 μˆ˜μ‹ ν–ˆμ„ λ•Œ μ‹€ν–‰ν•  μ½”λ“œ μž‘μ„±
  // 전솑받은 λ°μ΄ν„°λŠ” μ›ν•˜λŠ” μ΄λ¦„μ˜ νŒŒλΌλ―Έν„°λ‘œ λ°›μ•„ 좜λ ₯해보면 λœλ‹€.
})
  • 이제 데이터λ₯Ό μˆ˜μ‹ ν•˜κ³ μž ν•˜λŠ” κ³³μ—μ„œ μœ„μ™€ 같이 μ½”λ“œλ₯Ό μž‘μ„±ν•˜λ©΄ μˆ˜μ‹ μ΄ κ°€λŠ₯ν•˜λ‹€.
  • 일반적으둜 μˆ˜μ‹ ν•˜λŠ” μ½”λ“œλŠ” mounted() μ•ˆμ— μž‘μ„±ν•œλ‹€.

πŸ’œ ν•„ν„° 적용 νŽ˜μ΄μ§€μ—μ„œ μ„ νƒν•œ ν•„ν„°λ₯Ό μ—…λ‘œλ“œν•  이미지와 λ°œν–‰ν•œ 포슀트 이미지에 μ μš©ν•˜κΈ°

  • ν•„ν„° 적용 νŽ˜μ΄μ§€μ—μ„œ μ‚¬μš©μžκ°€ ν•˜λ‹¨μ˜ 미리 ν•„ν„°κ°€ μ μš©λ˜μ–΄ λ³΄μ—¬μ§€λŠ” μž‘μ€ 이미지듀(보라색 ν‘œμ‹œ) 쀑 ν•˜λ‚˜λ₯Ό ν΄λ¦­ν•˜λ©΄ μƒλ‹¨μ˜ 이미지(빨간색 ν‘œμ‹œ)에 μ‚¬μš©μžκ°€ μ„ νƒν•œ ν•„ν„°κ°€ 적용될 수 μžˆμ–΄μ•Ό ν–ˆλ‹€.
  • λ˜ν•œ λ‹€μŒ νŽ˜μ΄μ§€μΈ κΈ€ μž‘μ„± νŽ˜μ΄μ§€μ˜ 이미지에도, "λ°œν–‰" λ²„νŠΌμ„ λˆ„λ₯Έ ν›„ μƒˆλ‘œ μ—…λ‘œλ“œλ˜λŠ” 포슀트의 이미지에도 ν•„ν„°κ°€ λ‹Ήμ—°νžˆ μ μš©λ˜μ–΄ μžˆμ–΄μ•Ό ν–ˆλ‹€.

 

🀍 FilterBox.vue μ—μ„œ App.vue 둜 μ„ νƒν•œ ν•„ν„°λͺ… μ „μ†‘ν•˜κΈ°

// 전체 μ½”λ“œ 미리보기
// 전체 μ½”λ“œμ˜ λ‚΄μš©μ΄ κΈΈμ–΄ css 뢀뢄은 μ œμ™Έν–ˆλ‹€.

<template>
  <div class="item" @click="fire">
    <div class="slot">
      <slot></slot>
    </div>
    <div
      class="filter-item"
      :class="filter"
      :style="`background-image: url(${imgUrl})`"
    ></div>
  </div>
</template>

<script>
export default {
  name: "FilterBox",
  data() {
    return {};
  },
  props: {
    imgUrl: String,
    filter: String,
  },
  methods: {
    fire() {
      this.emitter.emit("filterName", this.filter);
    },
  },
};
</script>

<style>
</style>
  • κ·Έλž˜μ„œ FilterBox.vue μ—μ„œ item μ΄λΌλŠ” class λ₯Ό 가진 <div> μ˜μ—­μ„ ν΄λ¦­ν•˜λ©΄, 이 μ˜μ—­μ— μ μš©λ˜μ–΄μžˆλŠ” filter 이름이 App.vue 둜 μ „μ†‘λ˜λ„λ‘ ν•΄μ£Όμ—ˆλ‹€.
  • 이 λ•Œ, νƒœκ·Έ μžμ²΄μ— μ½”λ“œλ₯Ό μž‘μ„±ν•˜κΈ°μ—λŠ” λ‚΄μš©μ΄ κΈΈκΈ° λ•Œλ¬Έμ— methods 에 fire() ν•¨μˆ˜λ₯Ό λ”°λ‘œ λ§Œλ“€μ–΄μ„œ @click μ†μ„±μœΌλ‘œ λ„£μ–΄μ£Όμ—ˆλ‹€.
  • fire() ν•¨μˆ˜μ˜ μ½”λ“œλ₯Ό ν•΄μ„ν•˜μžλ©΄ μ‚¬μš©μžκ°€ ν΄λ¦­ν•œ filter-item μ΄λΌλŠ” class λ₯Ό 가진 <div> 의 λ°μ΄ν„°λ°”μΈλ”©λœ class 인 filter λ₯Ό filterName μ΄λΌλŠ” μ΄λ¦„μœΌλ‘œ 데이터 μ „μ†‘ν•΄λ‹¬λΌλŠ” 것이닀.
    (data λ³΄κ΄€ν•¨μ˜ ν•­λͺ©μ΄λ‚˜ props 의 ν•­λͺ©μ„ methods μ—μ„œ μ‚¬μš©ν•  λ•ŒλŠ” κΌ­ this λ₯Ό λΆ™μ—¬μ£Όμ–΄μ•Ό ν•œλ‹€.) 

🀍 App.vue μ—μ„œ  FilterBox.vue λ‘œλΆ€ν„° 전솑받은 데이터 μˆ˜μ‹ ν•˜κΈ°

// 전체 μ½”λ“œ 미리보기
// 전체 μ½”λ“œμ˜ λ‚΄μš©μ΄ κΈΈμ–΄ css 뢀뢄은 μ œμ™Έν–ˆλ‹€.

<template>
  <div class="header">
    <ul class="header-button-left">
      <li v-if="step === 1 || step === 2" @click="step = 0; filter = ''">Cancel</li>
    </ul>
    <ul class="header-button-right">
      <li @click="step++" v-if="step === 1">Next</li>
      <li @click="publish" v-if="step === 2">λ°œν–‰</li>
    </ul>
    <img src="./assets/logo.png" class="logo" />
  </div>

  <Container
    :posts="posts"
    :step="step"
    :imgUrl="imgUrl"
    :filter="filter"
    @content="content = $event"
  />

  <button class="btn" @click="more" v-if="step === 0">더보기</button>

  <div class="footer" v-if="step === 0">
    <ul class="footer-button-plus">
      <input @change="uploadImg" type="file" id="file" class="inputfile" />
      <label for="file" class="input-plus">+</label>
    </ul>
  </div>
</template>

<script>
import Container from "./components/Container.vue";
import posts from "./assets/posts";
import axios from "axios";

export default {
  name: "App",
  data() {
    return {
      posts: posts,
      moreCnt: 0,
      step: 0,
      imgUrl: "",
      content: "",
      filter: "",
    };
  },
  methods: {
    more() {
      axios
        .get(`https://codingapple1.github.io/vue/more${this.moreCnt}.json`)
        .then((result) => {
          this.posts.push(result.data);
          this.moreCnt++;
        })
        .catch(() => {
          console.log("GET μš”μ²­μ„ μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.");
        });
    },
    uploadImg(e) {
      let file = e.target.files;
      let url = URL.createObjectURL(file[0]);
      this.imgUrl = url;
      this.step++;
    },
    publish() {
      let myPost = {
        name: "Bak Seoyun",
        userImage: "https://placeimg.com/100/100/arch",
        postImage: this.imgUrl,
        likes: 28,
        date: "Jun 1",
        liked: false,
        content: this.content,
        filter: this.filter,
      };
      this.posts.unshift(myPost);
      this.step = 0;
      this.filter = "";
    },
  },
  components: {
    Container: Container,
  },
  mounted() {
    this.emitter.on("filterName", (a) => {
      this.filter = a;
    });
  },
};
</script>

<style>
</style>
  • App.vue 의 mounted() μ•ˆμ— FilterBox.vue μ—μ„œ μ „μ†‘ν•œ 데이터λ₯Ό μœ„와 같이 μ½”λ“œλ₯Ό μž‘μ„±ν•˜μ—¬ μˆ˜μ‹ ν•΄μ£Όμ—ˆλ‹€. 
  • μ½”λ“œλ₯Ό ν•΄μ„ν•˜μžλ©΄ filterName μ΄λΌλŠ” μ΄λ¦„μœΌλ‘œ λ°›μ•„μ˜¨ 데이터λ₯Ό App.vue 의 filter 에 μ μš©ν•΄λ‹¬λΌλŠ” 것이닀.
  • 그리고 μ΄λ ‡κ²Œ 적용된 filter λ₯Ό publish() ν•¨μˆ˜μ˜ filter ν•­λͺ©μ— μ μš©ν•΄μ£Όλ©΄, fiilter ν•­λͺ©μ— μ‚¬μš©μžκ°€ μ„ νƒν•œ ν•„ν„° 이름이 μž‘μ„±λœ μ±„λ‘œ ν¬μŠ€νŠΈκ°€ λ°œν–‰λœλ‹€.
    (ν—·κ°ˆλ¦¬μ§€ 말아야 ν•  것은 여기에 μ μš©ν•œλ‹€κ³  λ°œν–‰λœ 포슀트 이미지에 filter κ°€ μ μš©λ˜λŠ” 것은 μ•„λ‹ˆκ³ , 이 μž‘μ—…μ„ 해두어야 Post.vue μ—μ„œ λ°˜λ³΅λ¬Έμ„ 돌 λ•Œ post 의 class 에 μž‘μ„±λœ filter λ₯Ό μΆ”κ°€ν•  수 μžˆλ‹€.)
  • 그리고 publish() ν•¨μˆ˜ λ§ˆμ§€λ§‰μ— this.filter ="" λΌλŠ” μ½”λ“œλ₯Ό μΆ”κ°€ν•˜μ—¬ λ°œν–‰λœ μ΄ν›„μ—λŠ” μ„ νƒλœ filter κ°€ 없도둝 μ΄ˆκΈ°ν™”ν•΄μ£Όμ—ˆλ‹€.
    (이 μ½”λ“œλ₯Ό μž‘μ„±ν•˜μ§€ μ•ŠμœΌλ©΄, κ·Έ λ‹€μŒλ²ˆμ— 이미지λ₯Ό μ—…λ‘œλ“œν•  λ•Œ 아직 μ—…λ‘œλ“œν•  μ΄λ―Έμ§€μ˜ ν•„ν„°λ₯Ό μ„ νƒν•˜μ§€λ„ μ•Šμ•˜λŠ”λ° 이전에 μ„ νƒν–ˆλ˜ ν•„ν„°κ°€ μ μš©λ˜μ–΄ μžˆλŠ” 것을 λ°œκ²¬ν•˜κ²Œ λœλ‹€.)
  • ν•„ν„°κ°€ μ„ νƒλœ μƒνƒœμ—μ„œ "Cancel" λ²„νŠΌμ„ λˆ„λ₯Έ λ‹€μŒ, λ‹€μ‹œ 이미지λ₯Ό μ—…λ‘œλ“œν•˜λ €κ³  ν•˜λ©΄ κ·Έ 전에 μ„ νƒν–ˆλ˜ ν•„ν„°κ°€ μ μš©λ˜μ–΄ 있길래 이 κ²½μš°μ—λ„ μ„ νƒλœ filter κ°€ 없도둝 μ΄ˆκΈ°ν™”ν•˜λŠ” μ½”λ“œλ₯Ό μΆ”κ°€ν•΄μ£Όμ—ˆλ‹€. 

🀍 App.vue μ—μ„œ Container.vue 둜 filter μ „μ†‘ν•˜μ—¬ 이미지에 ν•„ν„° μ μš©ν•˜κΈ°

// 전체 μ½”λ“œ 미리보기
// 전체 μ½”λ“œμ˜ λ‚΄μš©μ΄ κΈΈμ–΄ css 뢀뢄은 μ œμ™Έν–ˆλ‹€.

<template>
  <div>
    <div v-if="step === 0">
      <Post :post="post" v-for="(post, i) in posts" :key="i" />
    </div>

    <!-- ν•„ν„°μ„ νƒνŽ˜μ΄μ§€ -->
    <div v-if="step === 1">
      <div
        class="upload-image"
        :class="filter"
        :style="{ backgroundImage: `url(${imgUrl})` }"
      ></div>
      <div class="filters">
        <FilterBox
          :filter="filter"
          :imgUrl="imgUrl"
          v-for="(filter, i) in filters"
          :key="i"
          >{{ filter }}</FilterBox
        >
      </div>
    </div>

    <!-- κΈ€μž‘μ„±νŽ˜μ΄μ§€ -->
    <div v-if="step === 2">
      <div
        class="upload-image"
        :class="filter"
        :style="{ backgroundImage: `url(${imgUrl})` }"
      ></div>
      <div class="write">
        <textarea
          class="write-box"
          @input="$emit('content', $event.target.value)"
          placeholder="λ‚΄μš©μ„ μž…λ ₯ν•˜μ„Έμš”!"
        ></textarea>
      </div>
    </div>
  </div>
</template>

<script>
import Post from "./Post.vue";
import FilterBox from "./FilterBox.vue";

export default {
  name: "Container",
  data() {
    return {
      filters: [
        "aden",
        "_1977",
        "brannan",
        "brooklyn",
        "clarendon",
        "earlybird",
        "gingham",
        "hudson",
        "inkwell",
        "kelvin",
        "lark",
        "lofi",
        "maven",
        "mayfair",
        "moon",
        "nashville",
        "perpetua",
        "reyes",
        "rise",
        "slumber",
        "stinson",
        "toaster",
        "valencia",
        "walden",
        "willow",
        "xpro2",
      ],
    };
  },
  components: {
    Post,
    FilterBox,
  },
  props: {
    posts: Array,
    step: Number,
    imgUrl: String,
    filter: String,
  },
};
</script>

<style>
</style>
  • ν•„ν„° 선택 νŽ˜μ΄μ§€μ™€ κΈ€ μž‘μ„± νŽ˜μ΄μ§€μ—λ„ λ‹Ήμ—°νžˆ μ„ νƒν•œ ν•„ν„°κ°€ 이미지에 적용이 λ˜μ–΄μ•Ό ν•˜κΈ°μ— 콜둠( : )을 μ‚¬μš©ν•˜μ—¬ class 속성에 filter λ₯Ό λ°μ΄ν„°λ°”μΈλ”©ν•˜μ—¬ λ„£μ–΄μ£Όμ—ˆλ‹€.

🀍 Post.vue λ₯Ό 톡해 각 post 의 이미지에 filter μ μš©ν•˜κΈ°

// 전체 μ½”λ“œ 미리보기
// 전체 μ½”λ“œμ˜ λ‚΄μš©μ΄ κΈΈμ–΄ css 뢀뢄은 μ œμ™Έν–ˆλ‹€.

<template>
  <div class="post">
    <div class="post-header">
      <div
        class="profile"
        :style="{ backgroundImage: `url(${post.userImage})` }"
      ></div>
      <span class="profile-name">{{ post.name }}</span>
    </div>
    <div
      class="post-body"
      :class="post.filter"
      :style="{
        backgroundImage: `url(${post.postImage})`,
      }"
    ></div>
    <div class="post-content">
      <p>{{ post.likes }} Likes</p>
      <p>
        <strong>{{ post.name }}</strong> {{ post.content }}
      </p>
      <p class="date">{{ post.date }}</p>
    </div>
  </div>
</template>

<script>
</style>
  • postImage κ°€ μ μš©λ˜λŠ” post-body λΌλŠ” class λ₯Ό 가진 <div> 에 class λ₯Ό ν•˜λ‚˜ 더 μΆ”κ°€ν•˜μ—¬ post.filter λ₯Ό λ°μ΄ν„°λ°”μΈλ”©ν•΄μ£Όμ—ˆλ‹€.
  • 이 μž‘μ—…μ„ 톡해 이제 각 posts 데이터에 ν¬ν•¨λœ filter ν•­λͺ©λ“€μ΄ μ‹€μ œλ‘œ 이미지에 적용이 λœλ‹€. 

 

728x90
λ°˜μ‘ν˜•