공부방
Vuex 본문
- Vue.js 애플리케이션에 대한 상태관리패턴+라이브러리
- 애플리케이션 모든 컴포넌트들의 중앙 저장소 역할(데이터 관리)
- 부모 자식 단계가 많이 복잡해진다면 데이터의 전달하는 부분이 매우 복잡해짐.
- 애플리케이션이 여러 구성 요소로 구성되고 더 커지는 경우 데이터를 공유하는 문제 발생
비 부모-자식간 통신
- 두 컴포넌트가 통신할 필요는 있지만 서로 부모/자식이 아닐수도 있다.
- 비어 있는 Vue Instance 객체를 Event Bus로 사용
상태 관리 패턴
- 상태는 앱을 작동하는 원본 소스(데이터)
- 뷰는 상태의 선언적 매핑입니다(상태를 보여주는 화면)
- 액션은 뷰에서 사용자 입력에 대해 반응적으로 상태를 바꾸는 방법(메소드)
Vuex 핵심 컨셉
Vuex 저장소 개념
- State : 단일 상태 트리 사용(데이터를 저장하는 곳), 애플리케이션마다 하나의 저장소를 관리(data)
- Getters : Vue Instance의 computed와 같은 역할, State를 기반으로 계산(computed)
- Mutations : State의 상태를 변경하는 유일한 방법(methods)
- Actions : 상태를 변이시키는 대신 액션으로 변이(Mutations)에 대한 커밋 처리(비동기 methods)
- module
State
- Vuex는 단일 상태 트리 사용
- 이 단일 객체는 모든 애플리케이션 수준의 상태를 포함하며 "원본 소스"의 역할
- 각 애플리케이션마다 하나의 저장소만 갖게 된다는 것을 의미
- 애플리케이션에서 공유해야 할 data 관리
- State에 접근 방식 : this.$store.state.데이터 이름
- computed를 사용하여 데이터를 가져와 사용 가능(값이 변경되면 해당 state를 공유하는 여러 컴포넌트의 DOM은 알아서 렌더링)
- 모든 상태를 Vuex에서 관리해야 하는 것은 아님
- Vuex에 저장하면 코드가 장황하고 간접적으로 변할수도 있다.
Getters
- State를 변경하지 않고 활용하여 계산을 수행(computed 속성과 유사)
- 실제 계산된 값을 사용하는 것처럼 getters는 저장소의 상태를 기준으로 계산
- computed 속성과 마찬가지로 state 종속성에 따라 캐시되고, 일부 종속성이 변경된 경우에만 다시 재계산
- getters 자체가 state를 변경하지는 않는다.
Mutations
- Vuex 저장소에서 실제로 상태를 변경하는 유일한 방법
- 각 컴포넌트에서 State의 값을 직접 변경하는 것은 권하지 않음
- mutation의 핸들러 함수는 반드시 동기적이어야 함.
(비동기 콜백함수의 실제로 호출 시기를 알 수 있는 방법이 없음. 추적x) - 첫번째 인자로 항상 state를 받음
- Mutations는 직접 호출이 불가능, store.commit('정의된 이름(메소드 이름)')으로 호출
- Actions에서 commit() 메서드에 의해 호출
Actions
- state를 변이시키는 대신 commit() 메서드를 통해 mutations호출
- 비동기 작업의 결과를 적용하려고 할 때 사용.(Backend API와 통신 등)
- context 객체 인자를 받음
(store.index.js 파일 내에 있는 모든 요소의 속성 접근 & 메서드 호출 가능)
(state를 직접 변경할 수 있지만 하지 말기 : 명확한 역할 분담을 하여 올바르게 상태 관리) - 컴포넌트에서 dispatch() 메서드에 의해 호출
Vuex 언제 사용?
- Vuex는 공유된 상태 관리를 처리하는데 유용하지만, 개념에 대한 이해와 시작하는 비용도 함께 발생
- 앱이 단순하다면 Vuex 없이도 괜찮다.(간단한 글로벌 이벤트 버스 OK)
- 중대형 규모의 SPA를 구축하는 경우
Vuex 설치
- CDN 방식
<script src="/path/to/vue.js"></script>
<script src="/path/to/vuex.js"></script> - NPM 방식
npm install vuex --save - Vue CLI
vue add vuex
(프로젝트를 진행하던 중에 추가를 하게 되면 App.vue를 덮으쓰므로 백업을 해두고 추가할 것)
main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App)
}).$mount('#app')
================================================================
store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
//공통의 데이터(상태)들을 저장하는 영역
},
getters: {
//state를 이용하여 원본의 데이터를 수정하지 않은 상태로 새로운 값을 뿌려주고 싶을때
//computed와 같은 역할을 하고 있음.
},
mutations: {
//state를 변경하는 유일한 방법 , 첫번째 인자로 state가 들어옴
//동기적으로 작성할 것!!
},
actions: {
//mutations를 호출
//backend api와 통신을 하는곳, context라고 하는 만능 객체가 인자로 들어옴
},
modules: {
//여러개로 쪼개놓고 관리를 하는 곳
},
});
컴포넌트 형태로 클릭하면 각자의 갯수와 전체 갯수 출력하기
components/ResultView.vue
<template>
<div>
<h2>전체 {{ total }} 번 클릭됨</h2>
<h3>{{ countMsg }}</h3>
</div>
</template>
<script>
export default {
name: "ResultView",
props: {
total: Number,
},
};
</script>
<style></style>
===============================================================
components/SubjectView.vue
<template>
<div>
<button @click="addCount">{{ title }}-{{ count }}</button>
</div>
</template>
<script>
export default {
name: "SubjectView",
props: {
title: String,
},
data() {
return {
count: 0,
};
},
methods: {
addCount() {
this.count += 1;
this.$emit("add-to-count");
},
},
};
</script>
<style></style>
===============================================================
App.vue
<template>
<div id="app">
<h2>당신이 좋아하는 파트를 선택하세요.</h2>
<!-- 사이트의 위에 버튼을 만들어주기 -->
<result-view :total="total"></result-view>
<subject-view @add-to-count="addTotalCount" title="코딩"></subject-view>
<subject-view @add-to-count="addTotalCount" title="알고"></subject-view>
</div>
</template>
<!-- vue를 생성하면 바로 import해주기 -->
<script>
import ResultView from "./components/ResultView.vue";
import SubjectView from "./components/SubjectView.vue";
// 이름을 정해주고
// import한 거 컴포넌트에 등록해주기
export default {
name: "App",
components: {
ResultView,
SubjectView,
},
data() {
return {
total: 0,
};
},
methods: {
addTotalCount() {
this.total += 1;
},
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
State사용
store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
//공통의 데이터(상태)들을 저장하는 영역
total: 0,
},
getters: {
//state를 이용하여 원본의 데이터를 수정하지 않은 상태로 새로운 값을 뿌려주고 싶을때
//computed와 같은 역할을 하고 있음.
},
mutations: {
//state를 변경하는 유일한 방법 , 첫번째 인자로 state가 들어옴
//동기적으로 작성할 것!!
},
actions: {
//mutations를 호출
//backend api와 통신을 하는곳, context라고 하는 만능 객체가 인자로 들어옴
},
modules: {
//여러개로 쪼개놓고 관리를 하는 곳
},
});
===============================================================
components/ResultView.vue
<template>
<div>
<h2>전체 {{ total }} 번 클릭됨</h2>
</div>
</template>
<script>
export default {
name: "ResultView",
computed: {
total() {
return this.$store.state.total;
},
},
};
</script>
<style></style>
===============================================================
components/SubjectView.vue
<template>
<div>
<button @click="addCount">{{ title }} - {{ count }}</button>
</div>
</template>
<script>
export default {
name: "SubjectView",
props: {
title: String,
},
data() {
return {
count: 0,
};
},
methods: {
addCount() {
this.count += 1;
//view 단에서 냅다 state에 접근을 하고 있는데
//이거 가넝하지만 하라고 말라고? --> 말라고~~ 연습이니까~~
this.$store.state.total++;
},
},
};
</script>
<style></style>
===============================================================
<template>
<div id="app">
<h2>당신이 좋아하는 파트를 선택하세요.</h2>
<result-view></result-view>
<subject-view title="코딩"></subject-view>
<subject-view title="알고"></subject-view>
</div>
</template>
<script>
import ResultView from "./components/ResultView.vue";
import SubjectView from "./components/SubjectView.vue";
export default {
name: "App",
components: {
ResultView,
SubjectView,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Getters
store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
//공통의 데이터(상태)들을 저장하는 영역
total: 0,
},
getters: {
//state를 이용하여 원본의 데이터를 수정하지 않은 상태로 새로운 값을 뿌려주고 싶을때
//computed와 같은 역할을 하고 있음.
//현재 total의 투표수에 따라 문구를 달리 출력하고 싶다.~~~
countMsg(state) {
let msg = "10회 ";
if (state.total > 10) msg += "초과";
else msg += "이하";
return `${msg} 호출됨`;
},
},
mutations: {
//state를 변경하는 유일한 방법 , 첫번째 인자로 state가 들어옴
//동기적으로 작성할 것!!
},
actions: {
//mutations를 호출
//backend api와 통신을 하는곳, context라고 하는 만능 객체가 인자로 들어옴
},
modules: {
//여러개로 쪼개놓고 관리를 하는 곳
},
});
================================================================
components/ResultView.vue
<template>
<div>
<h2>전체 {{ total }} 번 클릭됨</h2>
<h3>{{ countMsg }}</h3>
</div>
</template>
<script>
export default {
name: "ResultView",
computed: {
total() {
return this.$store.state.total;
},
countMsg: function () {
return this.$store.getters.countMsg;
},
// countMsg(){
// return this.$store.getters.countMsg
// }
},
};
</script>
<style></style>
================================================================
components/SubjectView.vue
<template>
<div>
<button @click="addCount">{{ title }} - {{ count }}</button>
</div>
</template>
<script>
export default {
name: "SubjectView",
props: {
title: String,
},
data() {
return {
count: 0,
};
},
methods: {
addCount() {
this.count += 1;
//view 단에서 냅다 state에 접근을 하고 있는데
//이거 가넝하지만 하라고 말라고? --> 말라고~~ 연습이니까~~
this.$store.state.total++;
},
},
};
</script>
<style></style>
================================================================
App.vue
<template>
<div id="app">
<h2>당신이 좋아하는 파트를 선택하세요.</h2>
<result-view></result-view>
<subject-view title="코딩"></subject-view>
<subject-view title="알고"></subject-view>
</div>
</template>
<script>
import ResultView from "./components/ResultView.vue";
import SubjectView from "./components/SubjectView.vue";
export default {
name: "App",
components: {
ResultView,
SubjectView,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
헬퍼를 사용한 mapgetters
나머지 코드는 위와 동일
components/ResultView.vue
<template>
<div>
<h2>전체 {{ total }} 번 클릭됨</h2>
<h3>{{ countMsg }}</h3>
</div>
</template>
<script>
//헬퍼를 이용하면 굉장히 편리해진다.
import { mapGetters } from "vuex";
export default {
name: "ResultView",
computed: {
//물론 state도 가넝하다.
//mapState라는걸 가져오면 (ㅎㅎ 직접 해볼것)
total() {
return this.$store.state.total;
},
...mapGetters(["countMsg"]),
},
};
</script>
<style></style>
동작도 위와 동일
mutations
store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
//공통의 데이터(상태)들을 저장하는 영역
total: 0,
},
getters: {
//state를 이용하여 원본의 데이터를 수정하지 않은 상태로 새로운 값을 뿌려주고 싶을때
//computed와 같은 역할을 하고 있음.
//현재 total의 투표수에 따라 문구를 달리 출력하고 싶다.~~~
countMsg(state) {
let msg = "10회 ";
if (state.total > 10) msg += "초과";
else msg += "이하";
return `${msg} 호출됨`;
},
},
mutations: {
//state를 변경하는 유일한 방법 , 첫번째 인자로 state가 들어옴
//동기적으로 작성할 것!!
ADD_ONE(state) {
state.total += 1;
},
//payload 숫자 값을 넘긴 상태이므로 바로 더할 수 있음
ADD_TEN(state, payload) {
state.total += payload;
},
//payload 객체가 들어왔다.
ADD_RANDOM(state, payload) {
state.total += payload.num;
},
},
actions: {
//mutations를 호출
//backend api와 통신을 하는곳, context라고 하는 만능 객체가 인자로 들어옴
},
modules: {
//여러개로 쪼개놓고 관리를 하는 곳
},
});
==============================================================
components/ResultView.vue
<template>
<div>
<h2>전체 {{ total }} 번 클릭됨</h2>
<h3>{{ countMsg }}</h3>
</div>
</template>
<script>
//헬퍼를 이용하면 굉장히 편리해진다.
import { mapGetters } from "vuex";
export default {
name: "ResultView",
computed: {
//물론 state도 가넝하다.
//mapState라는걸 가져오면 (ㅎㅎ 직접 해볼것)
total() {
return this.$store.state.total;
},
...mapGetters(["countMsg"]),
},
};
</script>
<style></style>
==============================================================
<template>
<div>
<button @click="addOneCount">{{ title }} + 1 - {{ count }}</button>
<button @click="addTenCount">{{ title }} + 10 - {{ count }}</button>
<button @click="addRandomCount">{{ title }} + ? - {{ count }}</button>
</div>
</template>
<script>
export default {
name: "SubjectView",
props: {
title: String,
},
data() {
return {
count: 0,
};
},
methods: {
addOneCount() {
this.count += 1;
//mutations는 직접 부를순없고 메서드를 통해서 부를 수 있음.
//commit('메서드이름'[,넘기고 싶은 인자])
this.$store.commit("ADD_ONE");
},
addTenCount() {
this.count += 10;
//값을 인자로 넘겼다.
this.$store.commit("ADD_TEN", 10);
},
addRandomCount() {
let num = Math.round(Math.random() * 100);
this.count += num;
//객체를 인자로 넘겼다.
this.$store.commit("ADD_RANDOM", { num });
},
},
};
</script>
<style></style>
==============================================================
App.vue
<template>
<div id="app">
<h2>당신이 좋아하는 파트를 선택하세요.</h2>
<result-view></result-view>
<subject-view title="코딩"></subject-view>
<subject-view title="알고"></subject-view>
</div>
</template>
<script>
import ResultView from "./components/ResultView.vue";
import SubjectView from "./components/SubjectView.vue";
export default {
name: "App",
components: {
ResultView,
SubjectView,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
첫번째 버튼을 누르면 1씩 증가 두번째 버튼을 누르면 10씩 증가 세번째 버튼을 누르면 랜덤으로 증가한다.
actions
store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
//공통의 데이터(상태)들을 저장하는 영역
total: 0,
},
getters: {
//state를 이용하여 원본의 데이터를 수정하지 않은 상태로 새로운 값을 뿌려주고 싶을때
//computed와 같은 역할을 하고 있음.
//현재 total의 투표수에 따라 문구를 달리 출력하고 싶다.~~~
countMsg(state) {
let msg = "10회 ";
if (state.total > 10) msg += "초과";
else msg += "이하";
return `${msg} 호출됨`;
},
},
mutations: {
//state를 변경하는 유일한 방법 , 첫번째 인자로 state가 들어옴
//동기적으로 작성할 것!!
ADD_ONE(state) {
state.total += 1;
},
//payload 숫자 값을 넘긴 상태이므로 바로 더할 수 있음
ADD_TEN(state, payload) {
state.total += payload;
},
//payload 객체가 들어왔다.
ADD_RANDOM(state, payload) {
state.total += payload.num;
},
},
actions: {
//mutations를 호출
//backend api와 통신을 하는곳, context라고 하는 만능 객체가 인자로 들어옴
// addOne(context) {
// // console.log(context);
// context.commit("ADD_ONE");
// },
addOne({ commit }) {
commit("ADD_ONE");
},
//비동기통신
asyncAddOne({ commit }) {
//ez하게 비동기 해보는 방법
// setTimeout(function () {
// commit("ADD_ONE");
// }, 2000);
setTimeout(() => {
commit("ADD_ONE");
}, 1000);
},
},
modules: {
//여러개로 쪼개놓고 관리를 하는 곳
},
});
=================================================================
components/SubjectView.vue
<template>
<div>
<button @click="addOneCount">{{ title }} + 1 - {{ count }}</button>
<button @click="addTenCount">{{ title }} + 10 - {{ count }}</button>
<button @click="addRandomCount">{{ title }} + ? - {{ count }}</button>
<button @click="asyncAddOne">{{ title }} 비동 - {{ count }}</button>
</div>
</template>
<script>
export default {
name: "SubjectView",
props: {
title: String,
},
data() {
return {
count: 0,
};
},
methods: {
addOneCount() {
this.count += 1;
//action이라고 하는것을 호출을 할텐데...
//mutations와 구분하기 위해서 메서드 이름을 카멜케이스를 사용했다.
this.$store.dispatch("addOne");
},
addTenCount() {
this.count += 10;
//값을 인자로 넘겼다.
this.$store.commit("ADD_TEN", 10);
},
addRandomCount() {
let num = Math.round(Math.random() * 100);
this.count += num;
//객체를 인자로 넘겼다.
this.$store.commit("ADD_RANDOM", { num });
},
//비동기도 actions를 호출해
asyncAddOne() {
this.count += 1;
this.$store.dispatch("asyncAddOne");
},
},
};
</script>
<style></style>
=================================================================
components/ResultView.vue
<template>
<div>
<h2>전체 {{ total }} 번 클릭됨</h2>
<h3>{{ countMsg }}</h3>
</div>
</template>
<script>
//헬퍼를 이용하면 굉장히 편리해진다.
import { mapGetters } from "vuex";
export default {
name: "ResultView",
computed: {
//물론 state도 가넝하다.
//mapState라는걸 가져오면 (ㅎㅎ 직접 해볼것)
total() {
return this.$store.state.total;
},
...mapGetters(["countMsg"]),
},
};
</script>
<style></style>
=================================================================
App.vue
<template>
<div id="app">
<h2>당신이 좋아하는 파트를 선택하세요.</h2>
<result-view></result-view>
<subject-view title="코딩"></subject-view>
<subject-view title="알고"></subject-view>
</div>
</template>
<script>
import ResultView from "./components/ResultView.vue";
import SubjectView from "./components/SubjectView.vue";
export default {
name: "App",
components: {
ResultView,
SubjectView,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
세번째까지 위와 동일하고 네번째 버튼을 누르면 1초 뒤에 누른게 올라간다.
'vue.js' 카테고리의 다른 글
axios응용 (0) | 2023.05.12 |
---|---|
Vuex응용(TodoList만들기) (0) | 2023.05.12 |
Vue Style Guide (0) | 2023.05.10 |
Vue Axios (0) | 2023.05.10 |
Vue Router (0) | 2023.05.09 |