개발 관련/Vue

[vue.js] Vuex를 이용한 데이터 처리

Hago하고 2021. 5. 19. 16:51
반응형

요즘 계속해서 일이 끝난 후 한~두시간씩 캡틴판교님의 강의를 보면서 vue.js를 익히고 있다.

비동기 처리, Promise, this 등 자바하고는 다른 개념이 많아서 처음엔 이해 안가는 부분들이 많았는데

듣다보니 이해가 되는 부분들이 생기는게 신기하다.

 

우선 이 글은 캡틴판교님의 인프런 강의 'Vue.js 완벽 가이드'를 참고로 했다. 사용하는 코드들도 해당 코드들이고, 티스토리에 따로 적는 이유는 단순히 보고 따라치는게 아니라 내가 제대로 이해한 것이 맞는지 확인할 겸 해서 올리는 것이다.

 

현재까지 진행된 상황은 이렇다.

클릭시 페이지가 바뀌도록 하기 위해 router를 설치,

routes 폴더의 index.js를 통해 각 패스를 통해 어떤 컴포넌트를 호출할지를 정의했다.

 

routes/index.js

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';
import ItemView from '../views/ItemView.vue';
import UserView from '../views/UserView.vue';

Vue.use(VueRouter);

export const router = new VueRouter({
    mode: 'history', //mode: history는 localhost뒤에 /#/을 삭제해주는 역할
  routes: [
    {
        path: '/',
        redirect: '/news'
    },
    {
        //path: url에 대한 정보
        path: '/news',
        //component : url 주소로 갔을 때 표시될 컴포넌트
        component: NewsView
    },
    {
        path: '/ask',
        component: AskView
    },
    {
        path: '/jobs',
        component: JobsView
    }    
  ]
});

component와 views 둘 다 vue 파일을 담고 있는데 이를 분리한 이유는 components 폴더에서는 툴바 등 변화가 잘 이뤄지지 않는 말 그대로 고정된 뷰고, views는 클릭에 따라 바뀌는 부분을 담고 있다.

 

news, ask, jobs 세 가지 탭이 존재하고 각각의 페이지에서 알맞은 정보를 axios로 받아와 뿌려준다.

각 탭에 정보를 받아와 title만 호출한 상태

각각의 정보를 받아오는 서버(API)는 따로 api폴더를 만들어 index.js로 주소를 정해주었다.

 

api/index.js

import axios from 'axios';

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

// 2. API 함수들을 정리
function fetchNewsList() {
    // 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`);
}

export {
    fetchNewsList,
    fetchJobsList,
    fetchAskList
}

각각 필요한 리스트들을 가져올 주소를 axios로 정해 export해서 컴포넌트에서 호출해서 부를 수 있도록 해놓았다.

 

우선 제일 처음 세팅한 AskView 컴포넌트를 보면

 

AskView.vue

<template>
  <div>
      <div v-for="ask in asks">{{ ask.title }}</div>
  </div>
</template>

<script>
import { fetchAskList } from '../api/index.js';
export default {
  data() {
    return {
      asks : []
    }
  },
  created() {
    var vm = this;
    fetchAskList()
    .then(function(response) {
      vm.asks = response.data;
    })
    .catch(function(error) {
      console.log(error);
    })
  }

}
</script>

<style>

</style>

여기서 created()에서 vm을 생성한 이유는

var vm = this를 생성하지 않고 바로 this.asks로 바로 data에 접근하면 현재 콜백이 현재의 뷰 컴포넌트를 바라보지 않기 때문에 오류가 난다. 이를 방지하기 위해 this를 객체로 지정해 var vm = this;로 생성을 해줘야 한다.

 

이후 vm.asks를 통해 데이터를 넘기면 제대로 넘어와 지는 것을 확인할 수 있다.

 

.then을 통해 로딩이 완료되면 데이터를 넘겨주기 때문에 성공적으로 데이터가 넘어오면 response.data가 vm.asks로 넘어가서, 이를 해당 컴포넌트에서 사용할 수 있다.

 


 

지금까지는 바로 api를 받아 컴포넌트로 뿌려주었지만,

이렇게 되면 안 좋은 점이 매번 api를 생성해주고 import로 index.js를 불러와 해당 함수를 등록해주어야한다.

코드가 복잡해짐은 물론 한 줄이라도 코드가 더 늘어나니 이를 줄일 수 있으면 좋을 것 같다.

 

그래서 사용하게 된 것이 Vuex 라이브러리.

vuex는 vue.js 애플리케이션 상태 관리 패턴 + 라이브러리라고 공식 홈페이지에서 설명하고 있다.

애플리케이션의 모든 컴포넌트에 대한 중앙 집중식 저장소를 제공하는 라이브러리다.

 

npm i vuex로 간단하게 설치할 수 있다.

공식홈페이지


vuex를 설치한 뒤, main.js파일에 이를 import 해도 되지만,

vuex는 state, mutations, actions 등 여러가지 속성들이 있고, 이를 활용해 데이터를 조작하기 때문에 다룰 데이터가 많아지고 설정할 것이 많아지면 애플리케이션의 구조를 확인할 수 있는 main.js가 복잡해진다.

 

이럴 때에는 따로 store 폴더를 만들어 index.js를 생성하고 이를 main.js에 등록하는 것이 코드를 분리하고 더 깔끔하게 다룰 수 있도록 도와준다.

 

main.js

//어플리케이션의 구조를 알 수 있는 구조도가 보여야한다.
import Vue from 'vue';
import App from './App.vue';
import { router } from './routes/index.js';
import { store } from './store/index.js';

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  router,
  store
}).$mount('#app')

 

그럼 이렇게 하고 store의 index.js를 확인해보자

 

/store/index.js

import Vue from 'vue';
import Vuex from 'vuex';
import { fetchNewsList } from '../api/index.js';

Vue.use(Vuex);

export const store = new Vuex.Store({
  state : {
    news: []
  },
  mutations: {
      //state로 데이터를 넘기기 때문에 첫 인자는 무조건 state로 해야한다.
    SET_NEWS(state, news) {
        state.news = news;
    }
  },
  actions : {
      FETCH_NEWS(context) {
        fetchNewsList()
        .then(response => {
            console.log(response);
            //구조상 actions에서 state로 바로 데이터를 바인딩 할 수 없다.
            //actions에서는 mutations을 거쳐 state로 가기 때문에
            //mutations에서 이를 담아주는 함수를 실행해야함.
            //actions에서는 mutations에 접근할 수 있게 context가 제공된다.
            //context.commit으로 해당 mutations에 접근할 수 있다.
            context.commit('SET_NEWS', response.data);
        })
        .catch(error => {
            console.log(error);
        });
      }
  }
});

Vue가 Vuex를 사용할 수 있게 Vue.use(Vuex)를 선언해준다.

이후 나오는 모든 속성들은 외부로 export되어 컴포넌트에서 사용되기 때문에 위와 같이 관리한다.

export const store = new Vuex.Store({
	state : {},
    mutations : {},
    actions : {},
    ...
});

 

actions에서는 데이터 api를 받아오는 역할, mutations에서는 데이터를 다루는 함수 등을 작성하고, state에서는 해당 데이터를 저장해 사용할 수 있도록 만드는 것으로 이해했다.

 

아래의 그림은 Vuex 홈페이지의 공식 문서에서 가져온 그림으로 Vuex의 Data Flow를 보여준다.

 

Actions에서 데이터를 가져온 뒤, 바로 State로 옮겨주면 정말 편하고 좋겠지만, 애석하게도 그러기 위해서는 Mutations를 거쳐야 가능하다. 

 

그럼 이를 숙지하고 데이터를 가져오는 actions를 먼저 보도록 하겠다.

 

store의 actions

actions : {
      FETCH_NEWS(context) {
        fetchNewsList()
        .then(response => {
            context.commit('SET_NEWS', response.data);
        })
        .catch(error => {
            console.log(error);
        });
      }
  }

FETCH_NEWS를 통해 routes에 정의해놓은 fetchNewsList()를 실행한다. 해당 함수는 axios를 통해 정해진 url에서 데이터를 받아오고 그 데이터가 성공적으로 들어오면(.then) context.commit을 통해 mutations로 해당 데이터를 넘겨준다는 뜻이다.

 

여기서 context는 actions에서 mutations에 접근할 수 있도록 제공하는 것으로 아래처럼 사용해 데이터 전달이 가능하다.

context.commit('전송할 mutations 함수의 이름', 전송할 데이터);

다시 코드를 보면 SET_NEWS라는 mutations으로 데이터가 넘어가게 된다. 그럼 해당 mutations를 보자

 

store의 mutations 

mutations: {
      //state로 데이터를 넘기기 때문에 첫 인자는 무조건 state로 해야한다.
    SET_NEWS(state, news) {
        state.news = news;
    }
  }

mutations에서는 이후 state로 데이터가 옮겨가기 때문에 첫 번째 인자로 꼭 state를 넣어야한다.

이전에 vm을 생성해서 데이터를 넣어야 전달이 되던 것 처럼 state 인자를 통해 state.변수이름 = 데이터;를 사용해야한다.

함수이름(state, 전달할 데이터 이름) {
	state.(state에 선언된 데이터 이름) = 전달할 데이터 이름
}

// ex

state : {
	message : []
},
mutations {
	sendMessage(state, M) {
    	state.message = M;
    }
},
actions {
	getMessage(context) {
    	fetchMessage()
        .then(response => {
        	context.commit('sendMessage', response.data);
        })
        .catch(error => {
        console.log(error);
        });
    }
}

위와 같이 완성이 되면 다시 데이터를 받아올 뷰 컴포넌트로 가서

<template>
  <div>
      <div v-for="user in this.$store.state.news">{{ user.title }}</div>
  </div>
</template>

<script>
export default {
  created() {
   this.$store.dispatch('FETCH_NEWS');
  }
}
</script>

<style>

</style>

위와같이 데이터를 받아올 수 있다.

이전 보다 코드가 훨씬 줄어든 것은 물론이고, data를 각 컴포넌트에서 다루지 않을 수 있어서 매우 편리하다.

store를 통해 정의한 state 안의 데이터들은 어느 컴포넌트에서든지 this.$store.state.데이터이름을 통해 호출할 수 있기 때문에 그만큼 관리가 편해진다.

 

created도 store/index.js에서 데이터를 부르는 actions를 선언하는 것 만으로 원하는 데이터를 호출할 수 있다.

 

오늘은 이렇게 vuex를 통한 데이터 관리에 대해 알아보았다.

중앙 집중식 저장소 하나가 생겼다고 이렇게까지 편해질줄은 상상도 못했다.

 

반응형