익명의 개발노트

[vue.js] high-order-component과 Mixin 본문

코딩일기/TIL

[vue.js] high-order-component과 Mixin

캡틴.JS 2019. 12. 26. 22:52
반응형

1. 스피너 같은거는 이벤트 버스로 통제해주는 것이 간결하다.

   버스는 라이프 싸이클 내부에서 사용
 이벤트 on했으면 off도 반드시 해주어야함,

 

2. 하이오더 컴포넌트

   1) 컴포넌트의 코드를 재사용할 수 있는 기술

   2) 단점: HOC로 통신하는 것이 많을 수록 컴포넌트 레벨이 깊어지면서 컴포넌트 통신에있어서 불편한 현상들이 발생함.

   

   ex) ask, jobs, news컴포넌트에서 api 콜하는 부분이 코드가 똑같다.(엔드포인트 주소는 다름)

인프런 캡틴 판교님 수업들으면서 작성한 해커뉴스

//ask.vue

created() {
 Bus.$emit("start:spinner");
  setTimeout(() => {
    this.$store
     .dispatch("FETCH_ASK")
     .then(() => {
        Bus.$emit("end:spinner");
      })
     .catch(error => {
        console.log(error);
     });
  }, 3000);
}
//jobs.vue

created() {
 Bus.$emit("start:spinner");
  setTimeout(() => {
    this.$store
     .dispatch("FETCH_JOBS")
     .then(() => {
        Bus.$emit("end:spinner");
      })
     .catch(error => {
        console.log(error);
     });
  }, 3000);
}
//news.vue

created() {
 Bus.$emit("start:spinner");
  setTimeout(() => {
    this.$store
     .dispatch("FETCH_NEWS")
     .then(() => {
        Bus.$emit("end:spinner");
      })
     .catch(error => {
        console.log(error);
     });
  }, 3000);
}

위 코드는 각 페이지 이동시 로딩바(스피너) 실행 후 3초후 데이터를 백엔드(리스트 보여지는 부분) 에서 받아와서 Vuex에 저장한 코드다.

 

위 코드를 HOC로 변경해보자.

 

1. src/view 폴더 안에  ListView를 만든다.

//ListView

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

<script>
import ListItem from "../components/ListItem.vue";
export default {
  components: {
    ListItem
  }
};
</script>

<style>
</style>

2. src/view 폴더안에 CreateListView.js파일을 만들고 공통되는 부분을 작성한다.

//하이오더 컴포넌트(CreateListView.js)

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

export default function createListView(name) {
  return {
    //재사용할 인스턴스(컴포넌트) 옵션들이 들어갈 자리
    name: name, //'NewsView' ,'JobsView' ,'AskView'
    created() {
      Bus.$emit("start:spinner");
      setTimeout(() => {
        this.$store
          .dispatch("FETCH_LIST", this.$route.name)
          .then(() => {
            Bus.$emit("end:spinner");
          })
          .catch(error => {
            console.log(error);
          });
      }, 3000);
    },
    render(createElement) {
      return createElement(ListView);
    }
  };
}

3. 실제 클릭하면 이동하는 부분인 라우터 쪽을 수정한다.

//src/routes/index.js

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

export const router = new VueRouter({
  mode: "history", //url # 제거
  routes: [
    {
      path: "/",
      redirect: "/news"
    },
    {
      //path: url주소
      //component : url 주소로 갔을때 컴포넌트
      path: "/news",
      name: "news",
      component: NewsView
    },
    {
      path: "/jobs",
      name: "jobs",
      component: JobsView
    },
    {
      path: "/ask",
      name: "ask",
      component: AskView
    },
  ]
 }

위 라우터 파일을 아래와 같이 변경한다.

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

export const router = new VueRouter({
  mode: "history", //url # 제거
  routes: [
    {
      path: "/",
      redirect: "/news"
    },
    {
      //path: url주소
      //component : url 주소로 갔을때 컴포넌트
      path: "/news",
      name: "news",
      component: createListView("NewsView") //하이오더 컴포넌트 적용
    },
    {
      path: "/jobs",
      name: "jobs",
      component: createListView("JobsView")
    },
    {
      path: "/ask",
      name: "ask",
      component: createListView("AskView")
    },
  ]
}  

컴포넌트에 CreateListView 를 정하고 입력한 컴포넌트의 이름을 값으로 넘겨준다.

//CreateListView.js

//하이오더 컴포넌트
import ListView from "./ListView.vue";
import Bus from "../utils/bus.js";

export default function createListView(name) {
  return {
    //재사용할 인스턴스(컴포넌트) 옵션들이 들어갈 자리
    name: name, //'NewsView' ,'JobsView' ,'AskView'
    created() {
      Bus.$emit("start:spinner");
      setTimeout(() => {
        this.$store
          .dispatch("FETCH_LIST", this.$route.name)
          .then(() => {
            Bus.$emit("end:spinner");
          })
          .catch(error => {
            console.log(error);
          });
      }, 3000);
    },
    render(createElement) {
      return createElement(ListView);
    }
  };
}

라우터에서 컴포넌트 이름을 넘겨주고 HOC에서 받아와서, this.$route.name으로 라우터 이름 달아주고, 

보여주고자 하는 컴포넌트 ListView를 랜더부분에 넣어주면된다.

 

4. Vuex의 Action, Mutations를 수정해준다. (애초에 Vuex에 데이터를 저장하기 때문에)

//action.js

import { 
    fetchNewsList, 
    fetchAskList, 
    fetchJobsList, 
    fetchUserInfo, 
    fetchCommentItem,
} from "../api/index.js";

export default {
    FETCH_NEWS(context){
        fetchNewsList()
        .then(res => {
            console.log(res);
            context.commit('SET_NEWS', res.data);
            return res; //spinner off해야함.
        })
        .catch(error => {
            console.log(error);
        })
      },
      FETCH_ASK(context){
        fetchAskList()
        .then(res => {
            console.log(res);
            context.commit('SET_ASK', res.data);
        })
        .catch(error => {
            console.log(error);
        })
      },
      FETCH_JOB({commit}){
        fetchJobsList()
        .then(res => {
            console.log(res);
            commit('SET_JOB', res.data);
        })
        .catch(error => {
            console.log(error);
        })
      },
      FETCH_USER({commit}, name){
        fetchUserInfo(name)
        .then( ({data}) => {
           
            commit('SET_USER', data);
        })
        .catch(error => {
            console.log(error);
        })
      },
      FETCH_ITEM({commit}, id){
        fetchCommentItem(id)
        .then( ({data}) => {
           
            commit('SET_ITEM', data);
        })
        .catch(error => {
            console.log(error);
        })
      },
}

위의 코드에서 FETCH_LIST를 추가하고 state에 list를 추가한다.

//src/store/index.js

import Vue from 'vue';
import Vuex from 'vuex';

import mutations from './mutations.js';
import actions from './actions';

Vue.use(Vuex)

export const store = new Vuex.Store({
  state: {
      news:[],
      asks:[],
      jobs:[],
      user:{},
      item:[],
      list:[],//추가
  },
  //getters는 컴포넌트의 computed임. 더 간결하게 쓰기위해서 스토어에 명시
  getters:{
    fetchedAsk(state){
        return state.asks;
    },
    fetchedItem(state){
        return state.item;
    }
  },
  //mutations:mutations, //같으니깐 mutations하나로 해도됨.
  mutations,
  actions,
})
//action.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));
            
        }
}

mutations.js에 SET_LIST 추가한다.

//mutations.js

export default {
    SET_NEWS(state, news){
        state.news = news;
    },
    SET_ASK(state, ask){
        state.asks = ask;
    },
    SET_JOB(state, jobs){
        state.jobs = jobs;
    },
    SET_USER(state, user){
        state.user = user;
    },
    SET_ITEM(state, item){
        state.item = item;
    },
    SET_LIST(state, list){
        state.list = list;
    },
}

데이터가 잘 나오는지 확인하면 끝.

 

공통적인 부분은 HOC로 만들어주고 거기서 네임이든 분기처리를 해주면된다.

 

참고자료: https://joshua1988.github.io/vue-camp/design/pattern5.html#%ED%95%98%EC%9D%B4-%EC%98%A4%EB%8D%94-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%A1%9C-%EB%B0%98%EB%B3%B5-%EC%BD%94%EB%93%9C-%EC%A4%84%EC%9D%B4%EA%B8%B0

 

5. Mixins

 1) Mixins으로 공통되는 부분을 뺄 수도 있다. 스피너 같은 부수적인 요소들을 빼면 굉장히 심플해진다. 

    때에 따라서는 CRUD도 뺄 수 있다.

 2) HOC보다 레이어가 얕음.(HOC: newsView -> listView -> listItem / Mixins : newsView -> listItem)

HOC
Mixins

 3) src 밑에 mixins폴더 생성 후 공통으로 사용하고자 하는 코드를 옮긴다.


//ListMixin.js
import Bus from "../utils/bus.js";

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

//newsView.vue

<template>
  <div>
    <list-item></list-item>
  </div>
</template>
<script>
import ListItem from "../components/ListItem.vue";
import ListMixins from "../mixins/ListMixin.js";

export default {
  components: {
    ListItem
  },
  mixins:[ListMixins],
};
</script>
//routes/index.js

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

export const router = new VueRouter({
  mode: "history", //url # 제거
  routes: [
    {
      path: "/",
      redirect: "/news"
    },
    {
      //path: url주소
      //component : url 주소로 갔을때 컴포넌트
      path: "/news",
      name: "news",
      component: NewsView
    },
    {
      path: "/jobs",
      name: "jobs",
      component: JobsView
    },
    {
      path: "/ask",
      name: "ask",
      component: AskView
    },
  ]
});

참고자료 : https://kr.vuejs.org/v2/guide/mixins.html

반응형
Comments