6 — 애플리케이션 제작 파트 — 스토어 구현
source: categories/study/vue-beginner-lv3/vue-beginner-lv3_6.md
6.1 Vuex 설치 및 Vuex가 적용된 앱 구조 소개
- Before: NewsView라는 페이지 컴포넌트에서 API의 함수를 그대로 가져다가 호출한 모습
- After: NewsView에서 바로 API 함수를 불러다 쓰는 것이 아니라 중간에 Vuex에 있는 state에 API로 불러온 데이터를 담아서 그걸로 화면에 표시
npm i vuex
# OR
yarn add vuex
6.2 Vuex 모듈화 및 state 적용
src/store/index.js 폴더 및 파일 생성
import Vue from "vue";
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
news: [],
}
})
export default store
src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from "./routes";
import store from "./store";
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router,
store,
}).$mount('#app')
6.3 NewsView에 actions와 mutations 적용
6.3.1 비동기 데이터 요청 로직 Vuex의 store의 actions로 옮기기
src/store/index.js
import Vue from "vue";
import Vuex from 'vuex';
import {fetchNewsList} from "../api";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
news: [],
},
actions: {
FETCH_NEWS() {
fetchNewsList()
.then(response => console.log(response))
.catch(error => console.log(error))
}
}
})
export default store
src/views/NewsView.vue
<template>
<div>
<div v-for="(user, index) in users" v-bind:key="index">{{user.title}}</div>
</div>
</template>
<script>
export default {
name: "NewsView",
data() {
return {
users: [],
}
},
created() {
this.$store.dispatch('FETCH_NEWS');
}
}
</script>
<style scoped>
</style>
Note
위와 같이 actions
에서 Backend API
에서 꺼내온 data
를 바로 State
에 넣고싶은데, 그렇게하면 안된다.
그 이유는 현재 Vuex의 구조는 비동기 로직은 전부 actions
에서 하게되어있고,
거기서 받아온 데이터를 mutations
를 통해서 State
에 넣어주게끔
그런 상태관리 구조로 되어있기 때문이다.
이는 데이터 흐름 추적
이 용이하게 하기위한 구조이다.
비동기 안에 데이터(state
)까지 조작하는 로직이 들어있으면 언제 어떻게 데이터(state
)가 바뀌는지 흐름 파악하기가 어렵다.
그래서 각각 역할을 정확하게 나눈 것이다.
actions
는 비동기 로직만을 다룸으로써 Backend API
로부터 데이터를 꺼내오기만 하고, State
에 담기위한 Mutations
를 별도로 호출하는 역할만 담당한다.
6.3.2 actions에서 mutations 호출하기
store/index.js
import Vue from "vue";
import Vuex from 'vuex';
import {fetchNewsList} from "../api";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
news: [],
},
mutations: {
SET_NEWS(state, news) {
state.news = news;
},
},
actions: {
FETCH_NEWS(context) {
fetchNewsList()
.then(response => {
context.commit('SET_NEWS', response.data);
})
.catch(error => console.log(error))
},
}
})
export default store
src/views/NewsView.vue
<template>
<div>
<div v-for="(user, index) in this.$store.state.news" v-bind:key="index">{{user.title}}</div>
</div>
</template>
<script>
export default {
name: "NewsView",
created() {
this.$store.dispatch('FETCH_NEWS');
}
}
</script>
<style scoped>
</style>
Note
위와 같이 하면 아까 말씀드렸던 위 이미지의 After로 구조가 바뀌게된다.
actions에서 API 로직을 호출해서 Backend API로부터 데이터를 받으면, 그걸 mutations로 넘겨주면서 mutations를 호출하고,
mutations에서 state 값을 수정한 다음, 이것이 NewsView 컴포넌트가 렌더링되면서 반영된다.
6.4 [실습 안내] JobsView와 AskView 실습 안내
src/store/index.js
import Vue from "vue";
import Vuex from 'vuex';
import {fetchNewsList, fetchJobsList, fetchAskList} from "../api";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
news: [],
jobs: [],
ask: [],
},
mutations: {
SET_NEWS(state, news) {
state.news = news;
},
SET_JOBS(state, jobs) {
state.jobs = jobs;
},
SET_ASK(state, ask) {
state.ask = ask;
}
},
actions: {
FETCH_NEWS(context) {
fetchNewsList()
.then(response => {
context.commit('SET_NEWS', response.data);
})
.catch(error => console.log(error))
},
FETCH_JOBS(context) {
fetchJobsList()
.then(response => {
context.commit('SET_JOBS', response.data);
})
.catch(error => console.log(error))
},
FETCH_ASK(context) {
fetchAskList()
.then(response => context.commit('SET_ASK', response.data))
.catch(error => console.log(error))
}
}
})
export default store
src/views/JobsView.vue
<template>
<div>
<div v-for="(job, index) in this.$store.state.jobs" v-bind:key="index">{{job.title}}</div>
</div>
</template>
<script>
export default {
name: "NewsView",
created() {
this.$store.dispatch('FETCH_JOBS');
}
}
</script>
<style scoped>
</style>
src/views/AskView.vue
<template>
<div>
<div v-for="(item, index) in this.$store.state.ask" v-bind:key="index">{{item.title}}</div>
</div>
</template>
<script>
export default {
name: "NewsView",
created() {
this.$store.dispatch('FETCH_ASK');
}
}
</script>
<style scoped>
</style>
6.5 [실습] JobsView에 스토어 적용
Destructuring 문법 적용
import Vue from "vue";
import Vuex from 'vuex';
import {fetchNewsList, fetchJobsList, fetchAskList} from "../api";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
news: [],
jobs: [],
ask: [],
},
mutations: {
SET_NEWS(state, news) {
state.news = news;
},
SET_JOBS(state, jobs) {
state.jobs = jobs;
},
SET_ASK(state, ask) {
state.ask = ask;
}
},
actions: {
FETCH_NEWS({commit}) {
fetchNewsList()
.then(({data}) => commit('SET_NEWS', data))
.catch(error => console.log(error))
},
FETCH_JOBS({commit}) {
fetchJobsList()
.then(({data}) => commit('SET_JOBS', data))
.catch(error => console.log(error))
},
FETCH_ASK({commit}) {
fetchAskList()
.then(({data}) => commit('SET_ASK', data))
.catch(error => console.log(error))
}
}
})
export default store
6.6 [실습] map 헬퍼 함수를 이용한 AskView 풀이
src/views/AskView.vue - mapState 사용
<template>
<div>
<div v-for="(item, index) in ask" v-bind:key="index">{{item.title}}</div>
</div>
</template>
<script>
import {mapState} from 'vuex';
export default {
name: "AskView",
computed: {
...mapState(['ask'])
},
created() {
this.$store.dispatch('FETCH_ASK');
}
}
</script>
<style scoped>
</style>
Vuex getters 사용
src/store/index.js
import Vue from "vue";
import Vuex from 'vuex';
import {fetchNewsList, fetchJobsList, fetchAskList} from "../api";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
news: [],
jobs: [],
ask: [],
},
getters: {
fetchedAsk(state) {
return state.ask;
}
},
mutations: {
SET_NEWS(state, news) {
state.news = news;
},
SET_JOBS(state, jobs) {
state.jobs = jobs;
},
SET_ASK(state, ask) {
state.ask = ask;
}
},
actions: {
FETCH_NEWS({commit}) {
fetchNewsList()
.then(({data}) => commit('SET_NEWS', data))
.catch(error => console.log(error))
},
FETCH_JOBS({commit}) {
fetchJobsList()
.then(({data}) => commit('SET_JOBS', data))
.catch(error => console.log(error))
},
FETCH_ASK({commit}) {
fetchAskList()
.then(({data}) => commit('SET_ASK', data))
.catch(error => console.log(error))
}
}
})
export default store
src/views/AskView.vue
<template>
<div>
<div v-for="(item, index) in fetchedAsk" v-bind:key="index">{{item.title}}</div>
</div>
</template>
<script>
import {mapGetters} from 'vuex';
export default {
name: "AskView",
computed: {
// # 3.
...mapGetters(['fetchedAsk'])
// 이름을 바꾸고싶다면
// ...mapGetters({
// ask: 'fetchedAsk',
// })
// # 2.
// ...mapState(['ask'])
// # 1.
// ask() {
// return this.$store.state.ask;
// }
},
created() {
this.$store.dispatch('FETCH_ASK');
}
}
</script>
<style scoped>
</style>
6.7 스토어 속성 모듈화 (state, getters, mutations, actions 속성별 모듈화입니다)
src/store/index.js
import Vue from "vue";
import Vuex from 'vuex';
import mutations from "./mutations";
import actions from "./actions";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
news: [],
jobs: [],
ask: [],
},
getters: {
fetchedAsk(state) {
return state.ask;
}
},
mutations,
actions,
})
export default store
src/store/actions.js
import {fetchAskList, fetchJobsList, fetchNewsList} from "../api";
export default {
FETCH_NEWS({commit}) {
fetchNewsList()
.then(({data}) => commit('SET_NEWS', data))
.catch(error => console.log(error))
},
FETCH_JOBS({commit}) {
fetchJobsList()
.then(({data}) => commit('SET_JOBS', data))
.catch(error => console.log(error))
},
FETCH_ASK({commit}) {
fetchAskList()
.then(({data}) => commit('SET_ASK', data))
.catch(error => console.log(error))
}
}
src/store/mutations.js
export default {
SET_NEWS(state, news) {
state.news = news;
},
SET_JOBS(state, jobs) {
state.jobs = jobs;
},
SET_ASK(state, ask) {
state.ask = ask;
}
}
스토어 기능별 모듈화
src/store/index.js
import Vue from "vue";
import Vuex from 'vuex';
import FetchNews from "./modules/FetchNews";
import FetchJobs from "./modules/FetchJobs";
import FetchAsk from "./modules/FetchAsk";
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
FetchNews,
FetchJobs,
FetchAsk,
},
})
export default store
src/store/modules/FetchAsk.js
import {fetchAskList} from "../../api";
const state = {
ask: [],
}
const getters = {
fetchedAsk(state) {
return state.ask;
},
}
const mutations = {
SET_ASK(state, ask) {
state.ask = ask;
},
}
const actions = {
FETCH_ASK({commit}) {
fetchAskList()
.then(({data}) => commit('SET_ASK', data))
.catch(error => console.log(error))
}
}
export default {
state,
getters,
mutations,
actions,
}
src/store/modules/FetchNews.js
import {fetchNewsList} from "../../api";
const state = {
news: [],
}
const getters = {
fetchedNews(state) {
return state.news;
},
}
const mutations = {
SET_NEWS(state, news) {
state.news = news;
},
}
const actions = {
FETCH_NEWS({commit}) {
fetchNewsList()
.then(({data}) => commit('SET_NEWS', data))
.catch(error => console.log(error))
},
}
export default {
state,
getters,
mutations,
actions,
}
src/store/modules/FetchJobs.js
import {fetchJobsList} from "../../api";
const state = {
jobs: [],
}
const getters = {
fetchedJobs(state) {
return state.jobs;
}
}
const mutations = {
SET_JOBS(state, jobs) {
state.jobs = jobs;
},
}
const actions = {
FETCH_JOBS({commit}) {
fetchJobsList()
.then(({data}) => commit('SET_JOBS', data))
.catch(error => console.log(error))
},
}
export default {
state,
getters,
mutations,
actions,
}
src/views/NewsView.vue
<template>
<div>
<div v-for="(user, index) in fetchedNews" v-bind:key="index">{{user.title}}</div>
</div>
</template>
<script>
import {mapGetters, mapActions} from "vuex";
export default {
name: "NewsView",
computed: {
...mapGetters(['fetchedNews']),
},
methods: {
...mapActions(['FETCH_NEWS']),
},
created() {
this.FETCH_NEWS();
}
}
</script>
<style scoped>
</style>
src/views/AskView.vue
<template>
<div>
<div v-for="(item, index) in fetchedAsk" v-bind:key="index">{{item.title}}</div>
</div>
</template>
<script>
import {mapGetters, mapActions} from 'vuex';
export default {
name: "AskView",
computed: {
// # 3.
...mapGetters(['fetchedAsk']),
// 이름을 바꾸고싶다면
// ...mapGetters({
// ask: 'fetchedAsk',
// })
// # 2.
// ...mapState(['ask'])
// # 1.
// ask() {
// return this.$store.state.ask;
// }
},
methods: {
...mapActions(['FETCH_ASK']),
},
created() {
this.FETCH_ASK();
}
}
</script>
<style scoped>
</style>
src/views/JobsView.vue
<template>
<div>
<div v-for="(job, index) in fetchedJobs" v-bind:key="index">{{job.title}}</div>
</div>
</template>
<script>
import {mapGetters, mapActions} from "vuex";
export default {
name: "JobsView",
computed: {
...mapGetters(['fetchedJobs']),
},
methods: {
...mapActions(['FETCH_JOBS']),
},
created() {
this.FETCH_JOBS();
}
}
</script>
<style scoped>
</style>