TODO LIST 만들어보기 Vue with Typescript (뷰 + 타입스크립트) (4)
Vuex 란?
☞ Vuex는 Vue.js 애플리케이션에 대한 상태 관리 패턴 + 라이브러리입니다. 애플리케이션의 모든 컴포넌트에 대한 중앙 집중식 저장소 역할을 하며 예측 가능한 방식으로 상태를 변경할 수 있습니다. 또한 Vue의 공식 devtools 확장 프로그램과 통합되어 설정 시간이 필요 없는 디버깅 및 상태 스냅 샷 내보내기/가져오기와 같은 고급 기능을 제공합니다.
(참고 : https://vuex.vuejs.org/kr/)
☞ 지금부터, Vuex 라이브러리로 ItemList를 JSON파일에서 불러오고, Component들에서 사용할 수 있도록 중앙 집중식 저장소 역할을 하는 Store 만들겠습니다.
※ Vuex 라이브러리에서도 개발자들이 편하게 Decorator(@Module, @Mutation, @Action)를 사용해서 Store를 만들 수 있게 해주는 'vuex-module-decorator' 라이브러리가 있습니다. (이것을 사용 할 것이기 때문에 아래 사이트에서 참고하면서 해주세요!)
https://github.com/championswimmer/vuex-module-decorators
☞ 먼저 vuex-module-decorator를 설치해줍니다.
npm install -D vuex-module-decorator
이후 /store에 index.ts를 만들어줍니다. (참고 : https://ko.nuxtjs.org/guide/vuex-store/)
☞ 만든 후에 vuex-module-decorator의 설명을 참고해서 아래와 같이 기본 틀을 만들어줍니다.
import { Module, Mutation, Action, VuexModule } from 'vuex-module-decorators';
@Module
export default class Store extends VuexModule{
}
(vuex-module-decorator)를 사용하지 않았을때 기본 틀,
const store = new Vuex.Store({
})
☞ vuex-module-decorator의 state는 아무 지정 없이 그냥 선언을 해줌.
import { Module, Mutation, Action, VuexModule } from 'vuex-module-decorators';
interface ItemList {
id: number;
content: string;
status: string;
}
@Module
export default class Store extends VuexModule{
itemList : ItemList[] = [
{ id: 1, content: '씻기', status: 'clear' },
{ id: 2, content: '준비하기', status: 'clear' },
{ id: 3, content: '학교가기', status: 'clear' },
{ id: 4, content: '게임하기', status: 'yet' },
]
}
☞ vuex-module-decorator의 mutation은 @Mutation을 사용해 선언함.
☞ 아이템 추가 Mutation - item 객체를 Create 컴포넌트에서 전달받아 push로 item 객체를 itemList에 넣어줌
@Mutation
addItem(item: ItemList){
this.itemList.push(item);
}
☞ 아이템 제거 Mutation - 아이템의 id를 List 컴포넌트에서 전달 받아 findIdx 함수로 넘겨받은 id와 똑같은 아이템의 idx를 찾아 splice로 그 인덱스의 아이템을 제거.
@Mutation
removeItem(id: number){
const idx = this.itemList.findIndex(function (item) {
return item.id === id
})
this.itemList.splice(idx , 1 );
}
☞ 아이템 상태 변경 Mutation - 아이템의 객체를 List 컴포넌트에서 전달받아, status 상태를 변경
@Mutation
changeStatus(item: ItemList){
const idx = this.itemList.findIndex(function (param) {
return param.id === item.id
})
if (item.status === 'yet'){
this.itemList[idx].status = 'clear';
} else{
this.itemList[idx].status = 'yet';
}
}
☞ status에 맞는 getters - status에 맞는 아이템들만 불러오기 위해 객체 filter함수를 사용
get setYet(){
return this.itemList.filter((item: ItemList) => item.status === 'yet')
}
get setClear(){
return this.itemList.filter((item: ItemList) => item.status === 'clear' )
}
/store/index.ts
import { Module, Mutation, Action, VuexModule } from 'vuex-module-decorators';
interface ItemList {
id: number;
content: string;
status: string;
}
@Module
export default class Store extends VuexModule{
itemList : ItemList[] = [
{ id: 1, content: '씻기', status: 'clear' },
{ id: 2, content: '준비하기', status: 'clear' },
{ id: 3, content: '학교가기', status: 'clear' },
{ id: 4, content: '게임하기', status: 'yet' },
]
//아이템 추가 Mutation
@Mutation
addItem(item: ItemList){
this.itemList.push(item);
}
//해당 아이템 제거 Mutation
@Mutation
removeItem(id: number){
const idx = this.itemList.findIndex(function (param) {
return param.id === id
})
this.itemList.splice(idx , 1 );
}
//해당 아이템 status 변경
@Mutation
changeStatus(item: ItemList){
const idx = this.itemList.findIndex(function (param) {
return param.id === item.id
})
if (item.status === 'yet'){
this.itemList[idx].status = 'clear';
} else{
this.itemList[idx].status = 'yet';
}
}
get setYet(){
return this.itemList.filter((item: ItemList) => item.status === 'yet')
}
get setClear(){
return this.itemList.filter((item: ItemList) => item.status === 'clear' )
}
}
※ 기본적인 기능을 하는 Store를 만들어 주었습니다. 이제 Store가 생겼으니 컴포넌트들에 함수를 추가해주고, Store의 기능들을 사용할 수 있도록 바꿔줘야 합니다.
변경된 List Component (/component/List.vue)
<template>
<div>
<div class="input-group" v-for="item in itemList" >
<span class="input-group-addon" id="basic-addon1">
<input type="checkbox" :checked="item.status == 'clear'" @change = "changeStatus(item)" >
</span>
<input type="text" class="form-control" :value="item.content"/>
<span class="input-group-btn">
<button class="btn btn-default" type="button" v-on:click="removeItem(item.id)">X</button>
</span>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import {Component, Watch} from 'vue-property-decorator'
interface ItemList{
id : number;
content : string;
status : string;
}
@Component
export default class List extends Vue{
itemList : ItemList[] = [];
created(){
this.itemList = this.$store.state.itemList;
}
initItem(){
this.itemList = this.$store.state.itemList;
}
removeItem(id: number){
this.$store.commit('removeItem', id );
this.initItem()
}
changeStatus(item: ItemList){
console.log(this.$route.params.status);
this.$store.commit('changeStatus', item );
this.initItem()
}
@Watch("$route.params.status")
routeUpdate(newValue: string){
if(!newValue){
this.itemList = this.$store.state.itemList;
}else if(newValue === 'yet'){
this.itemList = this.$store.getters.setYet;
}else{
this.itemList = this.$store.getters.setClear;
}
}
}
</script>
<style scoped>
</style>
설명
☞ created()란 해당 컴포넌트가 실행될 때, 맨 처음에 실행되어야 하는 기능을 created() 안에 넣어줌으로써 실행시킬 수 있다.
-> List 컴포넌트가 실행될 때, 컴포넌트의 itemList에 Store의 itemList로 초기화시켜주자
created(){
this.itemList = this.$store.state.itemList;
}
☞ commit을 사용해서 store의 mutation을 사용할 수 있으므로, x버튼을 눌렀을 때 v-on:click 기능으로 removeItem()이 실행되도록해서, commit을 사용해 store에 있는 removeItem mutation을 id를 넘기면서 실행시키자.
<button class="btn btn-default" type="button" v-on:click="removeItem(item.id)">X</button>
removeItem(id: number){
this.$store.commit('removeItem', id );
}
☞ @change를 사용해서 checkbox의 변화가 있을 때 changeStatus()가 실행되도록해서 store에 있는 changeStatus를 commit 해주자.
<input type="checkbox" :checked="item.status == 'clear'" @change = "changeStatus(item)" >
changeStatus(item: ItemList){
this.$store.commit('changeStatus', item );
this.initItem()
}
☞ @Watch를 사용해 $route.params.status의 값이 변경될 때마다, 그 값에 맞는 getters를 store에서 불러주자.
@Watch('$route.params.status')
routeUpdate(newValue: string){
if(!newValue){
this.itemList = this.$store.state.itemList;
}else if(newValue === 'yet'){
this.itemList = this.$store.getters.setYet;
}else{
this.itemList = this.$store.getters.setClear;
}
}
변경된 Create Component (/component/Create.vue)
<template>
<div>
<input type="text" class="input" v-model="content">
<button type="button" class= "btn" v-on:click="addItem()">+</button>
</div>
</template>
<script lang="ts">
import {Vue, Component} from 'vue-property-decorator'
@Component
export default class Create extends Vue{
content:string = ''
addItem(){
this.$store.commit('addItem', { id : this.$store.state.itemList.length+1, content : this.content, status : 'yet'})
}
}
</script>
<style scoped>
</style>
설명
List 컴포넌트와 비슷하게 v-on:click으로 addItem()을 실행시키고, addItem에서 store의 addItem mutation을 commit 해주자. 이때 item 객체를 넘겨줘야 하는데, id는 store에 있는 itemList의 길이에서 +1을 더한 값을 넘겨주면 된다.
<button type="button" class= "btn" v-on:click="addItem()">+</button>
addItem(){
this.$store.commit('addItem', { id : this.$store.state.itemList.length+1, content : this.content, status : 'yet'})
}
결과
(이번에는 변화되는 것들을 보여줘야 하기 때문에 영상으로 결과를 보여드리겠습니다.)
궁금하신 점이 있으시면 댓글을 달아주세요!!~