본문 바로가기
Dev/Vue.js

[Vue.js] HackerNews - Mixin과 하이 오더 컴포넌트

by dev_jsk 2020. 9. 24.
728x90
반응형

이벤트 버스

컴포넌트간에 데이터를 주고받는 방법

 

형식

// 비어있는 Vue 객체가 이벤트 버스
var eventBus = new Vue();

// 이벤트 발송
eventBus.$emit('이벤트명', 데이터);

// 이벤트 수신
eventBus.$on('이벤트명', function(데이터));

이벤트 버스를 통한 스피너 컴포넌트 만들기

페이지 이동 시 로딩바(스피너) 컴포넌트를 만들어보자

 

구현하기

1. src/components/Spinner.vue 생성

<template>
  <div class="lds-facebook" v-if="loading">
    <div></div>
    <div></div>
    <div></div>
  </div>
</template>

<script>
export default {
  props: {
    loading: {
      type: Boolean,
      required: true,
    },
  },
};
</script>

<style scoped>
.lds-facebook {
  display: inline-block;
  position: absolute;
  width: 64px;
  height: 64px;
  top: 47%;
  left: 47%;
}
.lds-facebook div {
  display: inline-block;
  position: absolute;
  left: 6px;
  width: 13px;
  background: #42b883;
  animation: lds-facebook 1.2s cubic-bezier(0, 0.5, 0.5, 1) infinite;
}
.lds-facebook div:nth-child(1) {
  left: 6px;
  animation-delay: -0.24s;
}
.lds-facebook div:nth-child(2) {
  left: 26px;
  animation-delay: -0.12s;
}
.lds-facebook div:nth-child(3) {
  left: 45px;
  animation-delay: 0;
}
@keyframes lds-facebook {
  0% {
    top: 6px;
    height: 51px;
  }
  50%,
  100% {
    top: 19px;
    height: 26px;
  }
}
</style>

2. src/utils/bus.js 생성

import Vue from 'vue';

// 이벤트 버스 생성
export default new Vue();

3. App.vueSpinner.vue 등록 및 필요 로직 추가

<template>
  <div id="app">
    <tool-bar></tool-bar>
    <transition name="page">
      <router-view></router-view>
    </transition>
    <spinner :loading="loadingStatus"></spinner>
  </div>
</template>

<script>
import ToolBar from "./components/ToolBar.vue";
import Spinner from "./components/Spinner.vue";
import bus from "./utils/bus.js";

export default {
  components: {
    ToolBar,
    Spinner,
  },
  data() {
    return {
      loadingStatus: false,
    };
  },
  methods: {
    startSpinner() {
      this.loadingStatus = true;
    },
    endSpinner() {
      this.loadingStatus = false;
    }
  },
  created() {
    // 이벤트 버스 실행
    bus.$on('start:spinner', this.startSpinner);
    bus.$on('end:spinner', this.endSpinner);
  },
  // 컴포넌트가 제거되기 전 실행되는 라이프사이클
  beforeDestroy() {
    // 이벤트 버스 해제
    bus.$off('start:spinner', this.startSpinner);
    bus.$off('end:spinner', this.endSpinner);
  }
};
</script>

4. NewsView, AskView, JobsView 코드 정리

<script>
import ListItem from "../components/ListItem.vue";
import bus from "../utils/bus.js";

export default {
  components: {
    ListItem,
  },
  created() {
    // App.vue로 스피터 시작 이벤트 전달
    bus.$emit("start:spinner");
    // 3000ms delay
    setTimeout(() => {
      // 데이터 요청
      // News: FETCH_NEWS, Ask: FETCH_ASK, Jobs: FETCH_JOBS
      this.$store.dispatch("actions 명")
        // 성공 시 수행
        .then(() => {
          // App.vue로 스피너 종료 이벤트 전달
          bus.$emit("end:spinner");
        })
        // 오류 시 수행
        .catch((error) => {
          console.log(error);
        });
    }, 3000);
  },
};
</script>

※ NewsView, AskView, JobsView 코드 동일. 데이터 요청 시 actions 명만 다르다.

 

동작확인

하이 오더 컴포넌트 (HOC)

뷰의 하이 오더 컴포넌트는 리액트의 하이 오더 컴포넌트에서 기원된 것으로 컴포넌트의 로직(코드)를 재사용하기 위한 방법 및 캡슐화(encapsulation)와 컴포넌트 추상화를 구현하는 방법. 컴포넌트의 로직을 훼손하지 않고 재사용성을 최대한 끌어올리겠다는 전략.

 

구현하기

1. 하이 오더 컴포넌트(src/views/CreateListView.js) 생성

// 하이 오더 컴포넌트 (HOC)

import ListView from "./ListView.vue";
import bus from "../utils/bus.js";

export default function createListView(name) {
  return {
    // 재사용할 인스턴스(컴포넌트) 옵션들이 들어갈 자리
    name,
    // NewsView, AskView, JobsView에서 실행한 이벤트를 HOC에 구성
    created() {
      bus.$emit("start:spinner");
      setTimeout(() => {
        // router name에 따라 News, Ask, Jobs 데이터 호출
        this.$store.dispatch("FETCH_LIST", this.$route.name)
          .then(() => {
            bus.$emit("end:spinner");
          })
          .catch((error) => {
            console.log(error);
          });
      }, 3000);
    },
    // render 함수를 통해 컴포넌트 로딩
    render(createElement) {
      return createElement(ListView);
    },
  };
}

2. src/routers/index.js 라우터 컴포넌트에 CreateListView.js 에서 선언한 render 함수 연결

import Vue from "vue";
import VueRouter from "vue-router";

// CreateListView import
import createListView from '../views/CreateListView.js';

Vue.use(VueRouter);

export const router = new VueRouter({
  mode: "history",
  routes: [
    {
      path: "/news",
      name: "news",
      component: createListView('NewsView'),
    },
    {
      path: "/ask",
      name: "ask",
      component: createListView('AskView'),
    },
    {
      path: "/jobs",
      name: "jobs",
      component: createListView('JobsView'),
    },
  ],
});

3. src/api/index.js 에 공통 리스트 API 함수 추가

import axios from "axios";

const config = {
  baseUrl: "https://api.hnpwa.com/v0/",
};

function fetchList(pageName) {
  return axios.get(`${config.baseUrl}${pageName}/1.json`);
}

export {
  fetchList,
};

4. src/store/actions.js 에 공통 리스트 조회 이벤트 추가

import {
  fetchList,
} from "../api/index.js";

export default {
  FETCH_LIST({ commit }, pageName) {
    fetchList(pageName)
      .then(({ data }) => commit('SET_LIST', data))
      .catch(error => console.log(error));
  },
};

5. src/store/mutations.js 에 공통 리스트 데이터 설정 함수 및 src/store/index.jsstate 속성 값 추가

// mutations.js
export default {
  SET_LIST(state, list) {
    state.list = list;
  },
};

// index.js
export const store = new Vuex.Store({
  state: {
    list: [],
  },
});

6. 공통 리스트 뷰(src/views/ListView.vue) 생성

<template>
  <div>
    <list-item></list-item>
  </div>
</template>

<script>
import ListItem from "../components/ListItem.vue";

export default {
  components: {
    ListItem,
  },
};
</script>

7. 공통 리스트 뷰 생성으로 src/components/ListItem.vue computed 속성 내 listItems() 함수에 뷰 명칭에 따른 데이터 분기처리가 필요 없기 때문에 제거

<script>
export default {
  computed: {
    listItems() {
      return this.$store.state.list;
    },
  },
};
</script>

※ 공통 리스트 뷰(ListView.vue) 생성으로 NewsView.vue, AskView.vue, JobsView.vue 에 관련된 로직과 파일은 더 이상 필요 없다.

Mixin

여러 컴포넌트 간에 공통으로 사용하고 있는 로직, 기능들을 재사용하는 방법

 

형식

var HelloMixins = {
  // 컴포넌트 옵션 (data, methods, created 등)
};

new Vue({
  mixins: [HelloMixins]
})

구현하기

1. src/mixins/ListMixin.js 생성

import bus from '../utils/bus.js'

export default {
  // 재 사용할 컴포넌트 옵션 & 로직
  created() {
    bus.$emit("start:spinner");
    this.$store
      .dispatch("FETCH_LIST", this.$route.name)
      .then(() => {
        console.log("fetched");
        bus.$emit("end:spinner");
      })
      .catch((error) => {
        console.log(error);
      });
  },
};

2. src/routers/index.jsNewsView, AskView, JobsView 컴포넌트 등록

import Vue from "vue";
import VueRouter from "vue-router";

import NewsView from "../views/NewsView.vue";
import AskView from "../views/AskView.vue";
import JobsView from "../views/JobsView.vue";

// HOC
import createListView from '../views/CreateListView.js';

Vue.use(VueRouter);

export const router = new VueRouter({
  mode: "history",
  routes: [
    {
      path: "/",
      redirect: "/news",
    },
    {
      path: "/news",
      name: "news",
      // HOC
      // component: createListView('NewsView'),
      // Mixin
      component: NewsView,
    },
    {
      path: "/ask",
      name: "ask",
      component: AskView,
    },
    {
      path: "/jobs",
      name: "jobs",
      component: JobsView,
    },
  ],
});

3. NewsView, AskView, JobsViewmixins 속성 추가

<template>
  <div>
    <list-item></list-item>
  </div>
</template>

<script>
import ListItem from "../components/ListItem.vue";
// mixin import
import ListMixin from "../mixins/ListMixin.js";

export default {
  components: {
    ListItem,
  },
  // mixins 속성 추가
  mixins: [ListMixin],
};
</script>

※ NewsView, AskView, JobsView 코드 동일.

하이 오더 컴포넌트(HOC)와 믹스인(Mixin)의 차이

HOC

일반적으로 HOC를 이용하여 컴포넌트를 구성하게 되면 컴포넌트 관계에서 층이 하나 더 생긴다.

  • 일반 : 상위 - 하위
  • HOC : 상위 - HOC - 하위

이처럼 HOC를 이용하여 개발 시 상,하위 컴포넌트의 로직을 수정하지 않고 기능을 확장해 나갈 수 있다. 그렇지만 HOC를 이용하면 컴포넌트 레이어를 복잡하게 만들어 컴포넌트간 주고받을 코드가 많아지게 된다.

Mixin

HOC 처럼 추가적인 컴포넌트가 생기지 않아 상대적으로 문법이 어렵지 않고 입문자에게 어려운 HOC의 사고 방식이 필요하지 않다. 하지만 컴포넌트 기능 테스트 측면에서는 HOC에 비해 기능 분리가 되어있지 않아 어렵다.

728x90
반응형

댓글