vuex
Vuex는 Vue.js 애플리케이션에 대한 상태 관리 패턴 + 라이브러리 입니다. 애플리케이션의 모든 컴포넌트에 대한 중앙 집중식 저장소 역할을 하며 예측 가능한 방식으로 상태를 변경할 수 있습니다.
공통의 상태를 공유하는 여러 컴포넌트 가 있는 경우 컴포넌트에서 공유된 상태를 추출하고 이를 전역 싱글톤으로 관리해야 합니다. 이를 통해 우리의 컴포넌트 트리는 커다란 "뷰"가 되며 모든 컴포넌트는 트리에 상관없이 상태에 액세스하거나 동작을 트리거 할 수 있습니다!
Flux
- MVC 패턴의 복잡한 데이터 흐름 문제를 해결하는 개발 패턴 - Unidirectional data flow
- action : 화면에서 발생하는 이벤트 또는 사용자의 입력
- dispatcher : 데이터를 변경하는 방법, 메서드
- model : 화면에 표시할 데이터
- view : 사용자에게 비춰지는 화면
MVC 패턴과 Flux 패턴 비교
- MVC 패턴 Controller -> Model <-> View
- 기능 추가 및 변경에 따라 생기는 문제점을 예측할 수가 없음.
- 앱이 복잡해지면서 생기는 업데이트 루프
- Flux 패턴 Action -> Dispatcher -> Model -> View
- 데이터의 흐름이 여러 갈래로 나뉘지 않고 단방향으로만 처리
Vuex 컨셉
- State : 컴포넌트 간에 공유하는 데이터 data()
- View : 데이터를 표시하는 화면 template
- Action : 사용자의 입력에 따라 데이터를 변경하는 methods
Vuex 구조
컴포넌트 -> 비동기 로직 -> 동기 로직 -> 상태
설치
<script src="https://unpkg.com/vuex"></script>
vuex는 싱글 파일 컴포넌트 체계에서 npm 방식으로 라이브러리를 설치하는게 좋다.
npm install vuex --save
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
모듈 시스템과 함께 사용하면 Vue.use()를 통해 Vuex를 명시적으로 추가해야 합니다. 전역 스크립트 태그를 사용할 때는 이 작업을 할 필요가 없습니다.
vuex 기술 요소
- state : 여러 컴포넌트에 공유되는 데이터
data
- getters : 연산된 state 값을 접근하는 속성
computed
- mutation : state 값을 변경하는 이벤트 로직 메서드
methods
- actions : 비동기 처리 로직을 선언하는 메서드
async methods
actions 비동기 코드 예제
/* store.js */
mutations: {
setData(state, fetchedData){
state.product = fetchedData;
}
},
actions:{
fetchProductData(context){
return axios.get('https://domain.com/products/1')
.then(response => context.commit('setData', response));
}
}
/* App.vue */
methods:{
getProduct(){
this.$store.dispatch('fetchProductData');
}
}
Store 속성들을 더 쉽게 사용하는 방법
- state -> mapState
- getters -> mapGetters
- mutations -> mapMutations
- actions -> mapActions
헬퍼의 사용법
- 헬퍼를 사용하고자 하는 vue 파일에서 아래와 같이 해당 헬퍼를 로딩
/* App.vue */
import { mapState } from 'vuex'
import { mapGetters } from 'vuex'
import { mapMutations } from 'vuex'
import { mapActions } from 'vuex'
export default{
computed(){
...mapState(['num']),
...mapGetters(['countedNum'])
},
methods:{
...mapMutations(['clickBtn']),
...mapActions(['asyncClickBtn'])
}
}
mapState
/* App.vue */
<!-- <p>{{ this.$store.state.num }}</p> -->
<p>{{ this.num }}</p>
import { mapState } from 'vuex'
computed(){
...mapState(['num'])
// num() { return this.$store.state.num; }
}
/* store.js */
state:{
num:10
}
mapGetters
/* App.vue */
<!-- <p>{{ this.$store.getters.reverseMessage }}</p> -->
<p>{{ this.reverseMessage }}</p>
import { mapGetters } from 'vuex'
computed(){
...mapGetters(['reverseMessage'])
}
/* store.js */
getters:{
reverseMessage(state){
return state.msg.split('').reverse().jogin('');
}
}
mapMutations
/* App.vue */
<button @click="clickBtn">popup message</button>
import { mapMutaions } from 'vuex'
methods: {
...mapMutations(['clickBtn']) ,
authLogin(){},
displayTable(){}
}
/* store.js */
mutations:{
clickBtn(state){
alert(state.msg);
}
}
mapActions
/* App.vue */
<button @click="delayClickBtn">delay popup message</button>
import { mapActions } from 'vuex'
methods: {
...mapActions([
'delayClickBtn', // 'delayClickBtn' : delayClickBtn
])
}
/* store.js */
actions:{
delayClickBtn(context){
setTimeout(() => context.commit('clickBtn'), 2000);
}
}
시작하기
# vue cli 프로젝트
vue create vuex-start
cd vuex-start
npm install vuex
/* src/main.js */
import Vue from 'vue'
import Vuex from 'vuex';
import App from './App.vue'
Vue.use(Vuex);
const store = new Vuex.Store({
state:{
count:0
},
mutations: {
increment: state => state.count++,
decrement: state => state.count--
}
});
new Vue({
render: h => h(App),
store,
}).$mount('#app')
/* src/App.vue */
<template>
<div id="app">
<p>{{ count }}</p>
<p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</p>
</div>
</template>
<script>
export default {
name: 'app',
computed: {
count () {
return this.$store.state.count
}
},
methods: {
increment () {
this.$store.commit('increment')
},
decrement () {
this.$store.commit('decrement')
}
}
}
</script>
<style>
</style>
예제1
/* main.js */
import Vue from 'vue'
import App from './App.vue'
import Vuex from 'vuex';
Vue.config.productionTip = false
Vue.use(Vuex);
let store = new Vuex.Store({
// data 속성
state: {
id: '...'
},
// methods 속성
mutations: {
changeId(state, id) {
state.id = id;
}
}
})
new Vue({
store,
render: h => h(App),
}).$mount('#app')
/* App.vue */
<template>
<div>
<app-header></app-header>
<app-content></app-content>
</div>
</template>
<script>
import AppHeader from './components/AppHeader.vue';
import AppContent from './components/AppContent.vue';
export default {
components: {
AppHeader,
AppContent,
},
}
</script>
/* components/AppHeader.vue */
<template>
<div>
<span>login as {{ this.$store.state.id }}</span>
</div>
</template>
/* components/AppContent.vue */
<template>
<div>
<login-form></login-form>
</div>
</template>
<script>
import LoginForm from './LoginForm.vue';
export default {
components: {
LoginForm,
}
}
</script>
/* components/LoginForm.vue */
<template>
<div>
<form>
<div>
<label for="username">ID : </label>
<input id="username" type="text" v-model="id">
</div>
<div>
<label for="password">PW : </label>
<input id="password" type="password" v-model="pw">
</div>
<button v-on:click.prevent="loginUser">login</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
id: '',
pw: '',
logMessage: '',
}
},
methods: {
loginUser() {
const loginId = this.id;
this.$store.commit('changeId', loginId);
}
}
}
</script>
예제2
/* main.js */
import Vue from 'vue'
import App from './App.vue'
import App2 from './App2.vue'
import { store } from './store/index.js';
new Vue({
el: '#app',
store,
render: h => h(App)
})
/* App.vue */
<template>
<div>
<span v-bind:class="errorClass">{{ reversedMessage }}</span>
</div>
</template>
<script>
export default {
data() {
return {
message: 'hi',
successClass: 'success',
isError: true,
}
},
computed: {
reversedMessage() {
return this.message + '!!';
},
errorClass() {
if (this.isError) {
return 'error';
} else {
return 'success';
}
}
}
}
</script>
<style scoped>
.error {
color: red;
}
.success {
color: blue;
}
</style>
/* App2.vue */
<template>
<div>
{{ this.$store.state.user }}
<button v-on:click="getUser">get user</button>
</div>
</template>
<script>
export default {
data() {
return {
message: ''
}
},
methods: {
getUser() {
this.$store.dispatch('FETCH_USER');
}
},
}
</script>
/* store/index.js */
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
// data
state: {
msg: 'hi',
user: {},
},
// computed
getters: {
exclaimMessage(state) {
return state.msg + '!!';
}
},
mutations: {
setUser(state, user) {
state.user = user;
}
},
// methods
actions: {
FETCH_USER(context) {
fetch('https://jsonplaceholder.typicode.com/users/1')
.then(response => response.json())
.then(json => {
context.commit('setUser', json);
})
.catch(error => console.log(error));
}
}
});
예제3
vue create vuex-token
cd vuex-token
npm install vuex
npm install vue-router
npm install axios
/* src/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')
/* src/App.vue */
<template>
<div>
<div>
<router-link to="/login">login view</router-link>
<router-link to="/main">main view</router-link>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {
/* created(){
this.$router.push('/login');
} */
}
</script>
<style scoped>
a{margin:0.3rem;}
</style>
/* src/routes/index.js */
import Vue from 'vue';
import VueRouter from 'vue-router';
import LoginView from '../views/LoginView.vue';
import MainView from '../views/MainView.vue';
import store from '../store/index.js';
Vue.use(VueRouter);
export const router = new VueRouter({
routes: [
{
path:'/',
redirect:'/login',
},
{
path: '/login',
component: LoginView,
},
{
path: '/main',
component: MainView,
beforeEnter: function(to, from, next){
if(store.state.token !== ''){
next();
}else{
alert('login');
}
}
}
]
})
/* src/store/index.js*/
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex); // 플러그인 셋업
export default new Vuex.Store({
state:{
token:''
},
mutations:{
setToken(state, token){
state.token = token;
}
}
})
/* src/views/LoginView.vue*/
<template>
<div>
<login-form></login-form>
</div>
</template>
<script>
import LoginForm from '../components/LoginForm.vue';
export default {
components:{
LoginForm,
}
}
</script>
<style>
</style>
/* src/views/MainView.vue*/
<template>
<div>
main
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
/* src/components/LoginForm.vue */
<template>
<div>
<form>
<div>
<label for="username">ID : </label>
<input id="username" type="text" v-model="username">
</div>
<div>
<label for="password">PW : </label>
<input id="password" type="password" v-model="password">
</div>
<div>
<label for="nickname">NICKNAME : </label>
<input id="nickname" type="text" v-model="nickname">
</div>
<button v-on:click.prevent="signupUser">sign up</button>
<button v-on:click.prevent="loginUser">login</button>
</form>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
username: '',
password: '',
nickname: '',
}
},
methods:{
signupUser(){
const data = {
username: this.username,
nickname: this.nickname,
password: this.password,
}
axios.post('http://localhost:3000/signup', data)
.then((response) =>{
console.log(response.data);
this.initForm();
})
.catch((error) => {
console.log(error);
});
},
loginUser(){
axios.post('http://localhost:3000/login', {
username: this.username,
password: this.password,
})
.then(response => {
const token = response.data.token;
this.$store.commit('setToken', token);
})
.catch(error => console.log(error));
},
initForm(){
this.username = '';
this.password = '';
this.nickname = '';
}
}
}
</script>
<style>
</style>
모던웹(NEMV) 혼자 제작 하기 3기 - 37 몽고 클라우드(ATLAS) 가입 및 테스트