vue.js/vue.js 2

63. <vue-news> 프로젝트 | 컴포넌트의 코드마저 재사용하는 하이 오더 컴포넌트

DEV-Front 2022. 8. 7. 17:48
반응형

vue의 하이 오더 컴포넌트는 리액트의 하이 오터 컴포넌트에서 기원된 것입니다.

리액트의 하이 오더 컴포넌트 소개 페이지를 보면

아래와 같이 정확한 정의가 나와 있습니다.

A higher-order-component (HOC) is an advanced technique in React for reusing component logic.

이 말을 정리해보면 다음과 같습니다.

 

하이 오더 컴포넌트의 컴포넌트의 로직(코드)을 재사용하기 위한 고급 기술 입니다.

 

1. 공통적으로 사용하는 로직을 컴포넌트 하나에 올리고

2. 그 컴포넌트를 내려받아 사용



routes 폴더 <index.js>

import Vue from 'vue'
import VueRouter from 'vue-router'
import ItemView from '../views/ItemView.vue'
import UserView from '../views/UserView.vue'
import createListView from '../views/CreateListView'

Vue.use(VueRouter);

export const router = new VueRouter({
    mode: 'history', // url #값 제거
    routes: [ //routes도 커졌을때 모듈화 가능
        {
            path: '/',// url에 대한 정보가 담기는곳, url주소
            redirect: '/news',// url 주소로 갔을때 표시될 컴포넌트
            
        },
        {
            path: '/news',             
            name: 'news',            
            component: createListView('NewsView'),
            // 하이 오더 컴포넌트
            // 기존에 있던 컴포넌트 위에 컴포넌트가 하나 더 생김

        },
        {
            path: '/ask',            
            name: 'ask',
            component: createListView('AskView'),
        },
        {
            path: '/jobs',
            name: 'jobs',
            component: createListView('JobsView'),
        },
        {
            path: '/item/:id',
            component: ItemView,
        },
        {
            path: '/user/:id',
            component: UserView,
        },
    ]
});

api 폴더 <index.js>

import axios from 'axios';


// 1. HTTP Requset & Response 와 관련된 기본 설정
const config = {
    baseUrl: 'https://api.hnpwa.com/v0/'
};

// 2. 공통 API 함수들 정리
function fetchNewsList(){
    // return 바로 해준게 핵심
    // return axios.get(config.baseUrl+'/news/1.json');
    return axios.get(`${config.baseUrl}news/1.json`);
}

function fetchJobsList(){
    return axios.get(`${config.baseUrl}jobs/1.json`)
}

function fetchAskList() {
    return axios.get(`${config.baseUrl}ask/1.json`)
}

// 하이 오더 컴포넌트 용
function fetchList(pageName) {
    return axios.get(`${config.baseUrl}${pageName}.json`)
}

function fetchUserInfo(username){
    // https://api.hnpwa.com/v0/user/32340433.json
    return axios.get(`${config.baseUrl}user/${username}.json`)
}

function fetchCommentItem(id){
    // https://api.hnpwa.com/v0/item/32340433.json
    return axios.get(`${config.baseUrl}item/${id}.json`)
}

// 3. 마지막 내보내기
export { fetchNewsList, fetchJobsList, fetchAskList, fetchUserInfo, fetchCommentItem, fetchList }

<action.js>

import { fetchNewsList, fetchAskList, fetchJobsList, fetchUserInfo, fetchCommentItem, fetchList } from '../api/index.js'

export default { // 1. 백엔드 API를 actions으로 받고 
    // FETCH_NEWS(context) {
    //     fetchNewsList()
    //         .then(res => {
    //             context.commit('SET_NEWS', res.data);
    //             return res;
    //         })
    //         .catch(error => console.log(error));
    // },
    // FETCH_ASKS({ commit }) {
    //     fetchAskList()
    //         .then(({ data }) => {
    //             commit('SET_ASKS', data);
    //         })
    //         .catch(error => console.log(error))
    // },
    // FETCH_JOBS({ commit }) {
    //     fetchJobsList()
    //         .then(({ data }) => {
    //             commit('SET_JBOS', 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({ commit }, pageName){
        fetchList(pageName)
            .then(({data}) => {
                commit('SET_LIST', data)
            })
            .catch(error => console.log(error))
    }
}

<mutation.js>

export default{ // 2. mutations으로 데이터 받아서
        // SET_NEWS(state, data) {
        //     state.news = data;
        // },
        // SET_ASKS(state, data) {
        //     state.asks = data;
        // },
        // SET_JBOS(state, data) {
        //     state.jobs = data;
        // },
        SET_USER(state, data){
            state.user = data;
        },
        SET_ITEM(state, data){
            state.items = data;
        },
        SET_LIST(state, data){
          state.list = data;
        }

    }

store 폴더 <index.js>

import Vue from 'vue'
import Vuex from 'vuex'
import mutations from './mutation.js';
import actions from './action.js'



Vue.use(Vuex);

// vuex는 상태관리 도구 
// 상태는 여러 컴포넌트 간에 공유되는 데이터 속성
export const store = new Vuex.Store({
    state : { // 3. state에 저장한다.
        // news : [],
        // asks : [],
        // jobs : [],
        user : {},
        items: {},
        list: [],
    },
    getters:{
        fetchedNews(state) {
            return state.news
        },
        fetchedAsk(state){
            return state.asks
        },
        fetchedJobs(state){            
            return state.jobs
        },
        fetchedItem(state){
            return state.items
        }
    },
    mutations, // 2. mutations으로 데이터 받아
    actions // 1. 백엔드 API를 actions으로 받고 
        
})

<CreateListView.js>

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

export default function createListView(name){
    return{        
        // 재사용할 인스턴스(컴포넌트) 옵션들이 들어갈 자리
        name: name,
        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);
                });          
        },
        render(createElement){
            return createElement(ListView);
        }
    }
}

<ListView.vue>

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

<script>
import listItem from '../components/listItem.vue';

export default {
    components:{
        listItem
    }
};
</script>

<style lang="scss" scoped>

</style>

<listItem.vue>

<template>
    <div>
        <ul class="news-list">
            <li v-for="(item, i) in listItems" v-bind:key="i" class="post">
                <!--포인트 영역-->
                <div class="points">
                    {{ item.points || 0 }}
                </div>
                <!-- 기타 정보 영역-->
                <div>
                    <!-- 타이블 -->
                    <p class="news-title">
                        <template v-if="item.domain">
                            <a v-bind:href="item.url">
                                {{ item.title }}
                            </a>
                        </template>
                        <template v-else>
                            <router-link v-bind:to="`item/${item.id}`">
                                {{ item.title }}
                            </router-link>
                        </template>
                    </p>

                    <small class="link-text">
                        {{ item.time_ago }}
                        by
                        <router-link v-if="item.user" v-bind:to="`/user/${item.user}`" class="link-text">
                                {{ item.user }}
                        </router-link>                        
                        
                        <a :href="item.url" v-else>
                                {{ item.domain }}
                        </a>
                    </small>
                </div>
            </li>
        </ul>
    </div>
</template>

<script>

export default {
    computed: {
        listItems() {
            return this.$store.state.list;
            // const routename = this.$route.name;

            // if (routename === 'news') {
            //     return this.$store.state.news;
            // } else if (routename === 'ask') {
            //     return this.$store.state.asks;
            // } else if (routename === 'jobs') {
            //     return this.$store.state.jobs;
            // } 
            // return this.$store.state.news;
        }
        
    },
};
</script>

<style scoped>
.news-list {
    margin: 0;
    padding: 0;
}

.post {
    list-style: none;
    display: flex;
    align-items: center;
    border-bottom: 1px solid #eee;
}

.points {
    width: 80px;
    height: 60px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #42b883;
}

.news-title {
    margin: 0;
}

.link-text {
    color: #828282;
}
</style>

 

반응형