Front-end/Vue

[Vue] 차근차근 Todo List 만들기 with typescript - (4) Vuex.Store 만들기

TODO LIST 만들어보기 Vue with Typescript (뷰 + 타입스크립트) (4)

Vuex 란? 

Vuex는 Vue.js 애플리케이션에 대한 상태 관리 패턴 + 라이브러리입니다. 애플리케이션의 모든 컴포넌트에 대한 중앙 집중식 저장소 역할을 하며 예측 가능한 방식으로 상태를 변경할 수 있습니다. 또한 Vue의 공식 devtools 확장 프로그램과 통합되어 설정 시간이 필요 없는 디버깅 및 상태 스냅 샷 내보내기/가져오기와 같은 고급 기능을 제공합니다.

(참고 : https://vuex.vuejs.org/kr/

 

Vuex가 무엇인가요? | Vuex

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

vuex.vuejs.org

지금부터,  Vuex 라이브러리로 ItemList를 JSON파일에서 불러오고, Component들에서 사용할 수 있도록 중앙 집중식 저장소 역할을 하는 Store 만들겠습니다.

 

※ Vuex 라이브러리에서도 개발자들이 편하게 Decorator(@Module, @Mutation, @Action)를 사용해서 Store를 만들 수 있게 해주는 'vuex-module-decorator' 라이브러리가 있습니다. (이것을 사용 할 것이기 때문에 아래 사이트에서 참고하면서 해주세요!)

https://github.com/championswimmer/vuex-module-decorators

 

championswimmer/vuex-module-decorators

TypeScript/ES7 Decorators to create Vuex modules declaratively - championswimmer/vuex-module-decorators

github.com

먼저 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'})
        }

결과

(이번에는 변화되는 것들을 보여줘야 하기 때문에 영상으로 결과를 보여드리겠습니다.)

 

 

궁금하신 점이 있으시면 댓글을 달아주세요!!~

반응형