송민준의 개발노트

Vuex 사용 본문

웹/Vue

Vuex 사용

송민준 2021. 3. 23. 00:34

vuex.vuejs.org/kr/

 

Vuex가 무엇인가요? | Vuex

Vuex가 무엇인가요? Vuex는 Vue.js 애플리케이션에 대한 상태 관리 패턴 + 라이브러리 입니다. 애플리케이션의 모든 컴포넌트에 대한 중앙 집중식 저장소 역할을 하며 예측 가능한 방식으로 상태를

vuex.vuejs.org

npm i vuex

 

상태

1. 단일 상태 트리

- vuex는 단일 상태 트리를 사용함. 이 단일 객체는 모든 어플리케이션 수준의 상태를 포함하며 "원본 소스"역할을 함. 이는 각 어플리케이션마다 하나의 저장소만 갖게 된다는 것을 의미.

 

- 아래는 mapState 헬퍼를 이용해서 관리

 

isAddBoard라는 모달상태값을  vuex의 저장소를 이용해서 관리

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    isAddBoard: false
  }
})

export default store

 

main.js

import Vue from 'vue'
import router from './router'
import App from './App.vue'
import store from './store'

new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})
<template>
    <AddBoard v-if="isAddBoard" @close="isAddBoard=false" @submit="onAddBoard"></AddBoard>
</template>
<script>
import AddBoard from './AddBoard.vue'
import {mapState} from 'vuex'

export default {
  components: {
    AddBoard
  },
  computed: {
    ...mapState([
      'isAddBoard'
    ]),
    // foo() {
    //
    // }
  },
  methods: {
  	addBoard() {
      this.isAddBoard = true
    }
  }
}

 

변이(mutation)

vuex 저장소에서 실제 상태를 변경하는 방법은 변이하는 것임. 이벤트와 매우 유사함.

동기적인 작업.

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    isAddBoard: false
  },
  mutations: {
    SET_IS_ADD_BOARD (state, toggle) {
      state.isAddBoard = toggle
    }
  }
})

export default store
<template>
  <div>
    <div class="home-title">Personal Boards</div>
    <div class="board-list" ref="boardList">
      <div class="board-item" v-for="b in boards" :key="b.id"
           :data-bgcolor="b.bgColor" ref="boardItem">
        <router-link :to="`/b/${b.id}`">
          <div class="board-item-title">{{b.title}}</div>
        </router-link>
      </div>
      <div class="board-item board-item-new">
        <a class="new-board-btn" href="" @click.prevent="SET_IS_ADD_BOARD(true)">
          Create new board...
        </a>
      </div>
    </div>
    <AddBoard v-if="isAddBoard" @close="isAddBoard=false" @submit="onAddBoard"></AddBoard>
  </div>
</template>

<script>
import {board} from '../api'
import AddBoard from './AddBoard.vue'
import {mapState, mapMutations} from 'vuex'

export default {
  components: {
    AddBoard
  },
  data() {
    return {
      loading: false,
      boards: [],
      error: ''
    }
  },
  computed: {
    ...mapState([
      'isAddBoard'
    ]),
    // foo() {
    //
    // }
  },
  created() {
    this.fetchData()
  },
  /**
   * --------  updated  ----------
   * 가상 DOM을 렌더링 하고 실제 DOM이 변경된 이후 호출됨.
   * 만약 변경된 값들을 DOM을 이용해 접근하고 싶다면, updated 훅이 적절
   * 여기서 data를 변경하는 것은 무한 루프를 일으킬 수 있음(주의)
   */
  updated() {
    // 컴포넌트의 reference 중에 boardItem을 찾아옴
    this.$refs.boardItem.forEach(el => {
      el.style.backgroundColor = el.dataset.bgcolor
    })
  },
  methods: {
    ...mapMutations([
      'SET_IS_ADD_BOARD'
    ]),
    fetchData() {
      this.loading = true
      board.fetch()
        .then(data => {
          this.boards = data.list
        })
        .finally(()=> {
          this.loading = false
        })
    },
    // 방법 1 - commit을 이용해서 상태 변경
    // addBoard() {
    //   // this.isAddBoard = true
    //   this.$store.commit('SET_IS_ADD_BOARD', true)
    // },
    onAddBoard(title) {
      board.create({title})
        .then(() => this.fetchData())
    }
  }
}
</script>

<style scoped>
.home-title {
  padding: 10px;
  font-size: 18px;
  font-weight: bold;
}
.board-list {
  padding: 10px;
  display: flex;
  flex-wrap: wrap;
}
.board-item {
  width: 23%;
  height: 100px;
  margin: 0 2% 20px 0;
  border-radius: 3px;
}
.board-item-new {
  background-color: #ddd;
}
.board-item a {
  text-decoration: none;
  display: block;
  width: 100%;
  height: 100%;
}
.board-item a:hover,
.board-item a:focus {
  background-color: rgba(0,0,0, .1);
  color: #666;
}
.board-item-title {
  color: #fff;
  font-size: 18px;
  font-weight: 700;
  padding: 10px;
}
.board-item a.new-board-btn {
  display: table-cell;
  vertical-align: middle;
  text-align: center;
  height: 100px;
  width: inherit;
  color: #888;
  font-weight: 700;
}
</style>
<template>
  <Modal>
    <div slot="header">
      <h2>
        Create new board
        <a href="" class="modal-default-button"
           @click.prevent="SET_IS_ADD_BOARD(false)">&times;</a>
      </h2>
    </div>
    <div slot="body">
      <form id="add-board-form"
            @submit.prevent="addBoard">
        <input class="form-control" type="text" v-model="input" ref="input">
      </form>
    </div>
    <div slot="footer">
      <button class="btn" :class="{'btn-success': valid}" type="submit"
              form="add-board-form" :disabled="!valid">
        Create Board</button>
    </div>
  </Modal>
</template>

<script>
import Modal from './Modal.vue'
import {mapMutations} from 'vuex'

export default {
  components: {
    Modal
  },
  data() {
    return {
      input: '',
      valid: false
    }
  },
  watch: {
    input(v) {
      this.valid = v.trim().length > 0
    }
  },
  mounted() {
    this.$refs.input.focus()
  },
  methods: {
    ...mapMutations([
      'SET_IS_ADD_BOARD'
    ]),
    // close() {
    //   this.$emit('close')
    // },
    addBoard() {
      // this.$emit('close')
      this.SET_IS_ADD_BOARD(false)
      this.$emit('submit', this.input)
    }
  }
}
</script>

<style scoped>

</style>

 

액션

액션은 변이와 유사함.

상태를 변이시키는 대신 액션으로 변이에 대한 커밋을 함.

작업에는 임의의 비동기 작업이 포함될 수 있음.

<template>
  <Modal>
    <div slot="header">
      <h2>
        Create new board
        <a href="" class="modal-default-button"
           @click.prevent="SET_IS_ADD_BOARD(false)">&times;</a>
      </h2>
    </div>
    <div slot="body">
      <form id="add-board-form"
            @submit.prevent="addBoard">
        <input class="form-control" type="text" v-model="input" ref="input">
      </form>
    </div>
    <div slot="footer">
      <button class="btn" :class="{'btn-success': valid}" type="submit"
              form="add-board-form" :disabled="!valid">
        Create Board</button>
    </div>
  </Modal>
</template>

<script>
import Modal from './Modal.vue'
import {mapMutations, mapActions} from 'vuex'

export default {
  components: {
    Modal
  },
  data() {
    return {
      input: '',
      valid: false
    }
  },
  watch: {
    input(v) {
      this.valid = v.trim().length > 0
    }
  },
  mounted() {
    this.$refs.input.focus()
  },
  methods: {
    ...mapMutations([
      'SET_IS_ADD_BOARD'
    ]),
    ...mapActions([
      'ADD_BOARD'
    ]),
    // close() {
    //   this.$emit('close')
    // },
    addBoard() {
      // this.$emit('close')
      this.SET_IS_ADD_BOARD(false)
      this.$emit('submit')
      this.ADD_BOARD({title: this.input})
    }
  }
}
</script>

<style scoped>

</style>
<template>
  <div>
    <div class="home-title">Personal Boards</div>
    <div class="board-list" ref="boardList">
      <div class="board-item" v-for="b in boards" :key="b.id"
           :data-bgcolor="b.bgColor" ref="boardItem">
        <router-link :to="`/b/${b.id}`">
          <div class="board-item-title">{{b.title}}</div>
        </router-link>
      </div>
      <div class="board-item board-item-new">
        <a class="new-board-btn" href="" @click.prevent="SET_IS_ADD_BOARD(true)">
          Create new board...
        </a>
      </div>
    </div>
    <AddBoard v-if="isAddBoard" @close="isAddBoard=false" @submit="onAddBoard"></AddBoard>
  </div>
</template>

<script>
import {board} from '../api'
import AddBoard from './AddBoard.vue'
import {mapState, mapMutations} from 'vuex'

export default {
  components: {
    AddBoard
  },
  data() {
    return {
      loading: false,
      boards: [],
      error: ''
    }
  },
  computed: {
    ...mapState([
      'isAddBoard'
    ]),
    // foo() {
    //
    // }
  },
  created() {
    this.fetchData()
  },
  /**
   * --------  updated  ----------
   * 가상 DOM을 렌더링 하고 실제 DOM이 변경된 이후 호출됨.
   * 만약 변경된 값들을 DOM을 이용해 접근하고 싶다면, updated 훅이 적절
   * 여기서 data를 변경하는 것은 무한 루프를 일으킬 수 있음(주의)
   */
  updated() {
    // 컴포넌트의 reference 중에 boardItem을 찾아옴
    this.$refs.boardItem.forEach(el => {
      el.style.backgroundColor = el.dataset.bgcolor
    })
  },
  methods: {
    ...mapMutations([
      'SET_IS_ADD_BOARD'
    ]),
    fetchData() {
      this.loading = true
      board.fetch()
        .then(data => {
          this.boards = data.list
        })
        .finally(()=> {
          this.loading = false
        })
    },
    // 방법 1 - commit을 이용해서 상태 변경
    // addBoard() {
    //   // this.isAddBoard = true
    //   this.$store.commit('SET_IS_ADD_BOARD', true)
    // },
    onAddBoard() {
      this.fetchData()
    }
  }
}
</script>

<style scoped>
.home-title {
  padding: 10px;
  font-size: 18px;
  font-weight: bold;
}
.board-list {
  padding: 10px;
  display: flex;
  flex-wrap: wrap;
}
.board-item {
  width: 23%;
  height: 100px;
  margin: 0 2% 20px 0;
  border-radius: 3px;
}
.board-item-new {
  background-color: #ddd;
}
.board-item a {
  text-decoration: none;
  display: block;
  width: 100%;
  height: 100%;
}
.board-item a:hover,
.board-item a:focus {
  background-color: rgba(0,0,0, .1);
  color: #666;
}
.board-item-title {
  color: #fff;
  font-size: 18px;
  font-weight: 700;
  padding: 10px;
}
.board-item a.new-board-btn {
  display: table-cell;
  vertical-align: middle;
  text-align: center;
  height: 100px;
  width: inherit;
  color: #888;
  font-weight: 700;
}
</style>
import Vue from 'vue'
import Vuex from 'vuex'
import * as api from '../api'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    isAddBoard: false
  },
  mutations: {
    SET_IS_ADD_BOARD (state, toggle) {
      state.isAddBoard = toggle
    }
  },
  actions: {
    ADD_BOARD(_, {title}) {
      return api.board.create(title)
    }
  }
})

export default store

 

' > Vue' 카테고리의 다른 글

Vue Lazy Loading  (2) 2021.07.20
Vue - EventBus 정리  (0) 2021.07.15
axios 사용  (0) 2021.03.21
Vue 토이프로젝트 github에 올리기(Atom, git bash 사용)  (0) 2020.02.14
vue local storage에 값 넣기(로컬 스토리지)  (0) 2020.02.14