[vue.js] todo-list 만들기

꿀이·2022년 6월 4일
0
post-thumbnail

왜? vue.js 를 공부하는지

  • 타임리프로 개발을 해도 되지만... 어차피 화면 개발을 하려면 html,css,js를 공부 해야한다. (나는 혼자다... 내가 만들자...)
  • 요즘은 백엔드 개발자들이 api로 개발해서 json 으로 데이터를 많이 넘겨준다고 한다.

이왕하는거 요즘 백엔드 개발자들도 vue.js 를 많이 한다니까... 나도 해보자!

인프런의 캡틴판교님의 Vue.js 중급 강좌 강의를 들으면서 vuejs로 화면을 개발하는 법을 배우면서 기록해두자.


목차


템플릿 문법

  • v-on:click="매핑되는 메서드 명" : 클릭을 할 때 실행되는 메서드와 매핑시켜준다

  • v-on:keyup.enter="addTodo" : 마우스 클릭이 아닌 enter 키를 통해서 입력가능

  • v-bind:key="xxxx" : 뷰 인스턴스의 데이터 속성을 해당 HTML 요소에 연결

  • v-model="xxxx" : v-bind & v-on 을 합쳐놓은것 (사용자가 입력하면 자동으로 데이터가 없데이트 됨) ※ 근데 한글 같은 경우는 마지막 글자가 한번 씹히는 문제가 있는듯

  • v-for="(todoItem,index) in todoItems" v-bind:key="todoItem.item" : 연결된 뷰 데이터 를 for문 돌릴 수 있다. todoItem 만 넣고 돌려도 됨 index 를 추가하면 자동으로 인덱스를 매겨줌, 이때 v-bind:key="xxx" 여기는 유니크한 String or 숫자 만 가능

  • v-bind:class="{적용할 css: 특정 값}" : 특정 값의 true,false 여부에 따라서 css를 적용할 수 있다.

  • v-bind:내려보낼 프롭스 속성 이름="현재 위치의 컴포넌트 데이터 속성"

  • v-on:하위 컴포넌트에서 발생시킨 이벤트 이름="현재 컴포넌트의 메서드 명" : emit 을 받을 때


컴포넌트 등록

Components 폴더 아래에 우리가 만들 컴포넌트 들을 작성해주자. 단위가 작을수록 재사용성이 좋다. 그리고 나서 App.vue 에서 컴포넌트 속성에 등록을 한 후에 template 부분에 해당 컴포넌트를 사용할 수 있 다.


각종 폰트 및 아이콘 설정

public 폴더 아래에 index.html 에 폰트 및 아이콘 관련된 link 를 걸어 주자

    <!-- 반응형 설정 -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- 파비콘 설정 -->
    <link rel="shortcut icon" href="/src/assets/favicon.ico" type="image/x-icon">
    <link rel="icon" href="/favicon.ico" type="image/x-icon">

    <!-- <link rel="icon" href="<%= BASE_URL %>favicon.ico"> -->

    <!-- awesomefont 설정 -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.2/css/all.min.css"/>

    <!-- 구글 폰트 ubuntu -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@1,700&display=swap" rel="stylesheet">

컴포넌트 개발

Input컴포넌트 개발하기 (입력받기)

Input 컴포넌트는 사용자가 입력을 하면 해당 input 값을 저장할 수 있도록 할거다 (나중에 vuex로 하고 여기서는 로컬 스토리지에 저장, 이후에는 스프링부트 서버로 post 하는걸로 한번 해보자)

  • v-model="매핑되는 변수 명" : input box 의 입력값을 변수와 매핑시켜준다
  • v-on:click="매핑되는 메서드 명" : 클릭을 할 때 실행되는 메서드와 매핑시켜준다
  • v-on:keyup.enter="addTodo" : 마우스 클릭이 아닌 enter 키를 통해서 입력가능

여기서 변수를 그냥 선언하는게 아니라 함수에서 객체형식으로 넘겨준다는걸 기억하자. 이렇게 약속이 되어 있는듯?
메서드의 경우도 객체로 만들고 그안에 함수명 : function(){xxx} 이런식으로 매핑시켜서 사용하는듯

그리고 v-model 을 사용하면 입력창 & data 가 동기화가 되는듯 즉각적으로 반영이 됨. (addTodo 메서드에서 로컬 스토리지에 저장한 후에 this.newTodoItem='' 로 초기화를 해주는데 이러면 바로 웹페이지에서도 적용이됨)

<template>
  <div>
      <input type="text" v-model="newTodoItem">
      <button v-on:click="addTodo">add</button>
  </div>
</template>

<script>
export default {
    data: function(){
        return {
            newTodoItem: "",
            testItem:"",
        }
    },
    methods: {
        addTodo: function(){
            console.log(this.newTodoItem);
            //로컬 스토리지에 저장해보자
            localStorage.setItem(this.newTodoItem,this.newTodoItem);
            this.clearInput();//내부 함수도 this 로 접근이 가능하네
        },
        clearInput: function(){
            //입력 박스 초기화
            this.newTodoItem='';
        }
    }
}
</script>

css 입히기

버튼이 아니라 span 태그를 활용해서 아이콘을 입힐 수 있다.

<template>
  <div class="inputBox shadow">
    <input type="text" v-model="newTodoItem" v-on:keyup.enter="addTodo">
    <!-- <button v-on:click="addTodo">add</button> -->
    <span class="addContainer" v-on:click="addTodo">
        <i class="fas fa-plus addBtn"></i>
    </span>

    <span>
        
    </span>
  </div>
</template>

List 컴포넌트 개발하기 (v-for 출력)

localStorage 에 있는 값들을 created시점에 todoItems 에 넣어준 후에 v-for로 돌려줄거다. 콧수염 괄호 {{...}} 를 통해서 해당 item 에 접근할 수 있다.

  • created() 는 뷰 인스턴스의 라이프사이클의 맨처음에 실행되는 함수라고 생각하자

    약간 이런곳에서 vuex 가 필요한듯? input 에서 추가한 값들을 공통된 곳에서 관리를 하면 굳이 created() 시점에서 for문으로 초기화 할 필요가 없을듯

<template>
  <div>
      <ul>
          <li v-for="todoItem in todoItems" v-bind:key="todoItem">
              {{todoItem}}
          </li>
      </ul>
      
  </div>
</template>

<script>
export default {
    data: function(){
        return{
            todoItems:[],
        }
    },
    created:function(){
        //뷰 인스턴스가 생성되자마자 실행되는 lifecycle 훅
        if(localStorage.length > 0) {
            for(var i = 0 ; i<localStorage.length ; i++){
                //이게 웹펙 뭐시깽이가 디폴트로 들어가는듯? 그래서 이거는 빼주려고
                if(localStorage.key(i) !== 'loglevel:webpack-dev-server'){
                    this.todoItems.push(localStorage.key(i));
                }
            }
        }
    }

}
</script>

리스트 삭제

쓰레기통 아이콘을 추가를 해줬고 해당 아이콘을 클릭하면 삭제를 해줄거다. removeTodo(삭제할 값, 인덱스) 를 호출해주면 된다. 이때 v-for 에서 지원해주는 v-for="(todoItem,index) in todoItems" 이런식으로 하면 자동적으로 해당 Item 의 인덱스를 매겨준다.

localStorage 에서 todoItem 으로 삭제하면 localStorage 에서는 반영이 되지만 화면에서는 반영이 안된다. 그래서 splice(index,1) 을 통해서 해당인덱스에서 한개의 Item 을 삭제해준다.

아니 근데 삭제할 때는 화면에 즉각적으로 반영이 되는데, 왜 추가할때는 새로고침을 해야지 화면이 갱신되는거지?
--> 라고 생각을 했는데 이게 지금 input 컴포넌트랑 list 컴포넌트가 분리가 되어 있어서 모르는 거였구나 :) pros나 emit 을 하거나 vuex를 써야겠네?

<template>
  <div>
      <ul>
          <li v-for="(todoItem,index) in todoItems" v-bind:key="todoItem" class="shadow">
            {{todoItem}}
            <span class="removeBtn" v-on:click="removeTodo(todoItem, index)">
                <i class="fas fa-trash-alt"></i>
            </span>
          </li>
      </ul>
  </div>
</template>

<script>
export default {
    data: function(){
        return{
            todoItems:[],
        }
    },
    methods: {
        removeTodo : function(todoItem, index){
            localStorage.removeItem(todoItem);
            this.todoItems.splice(index,1);
        }
    },
    created:function(){
        //뷰 인스턴스가 생성되자마자 실행되는 lifecycle 훅
        if(localStorage.length > 0) {
            for(var i = 0 ; i<localStorage.length ; i++){
                //이게 웹펙 뭐시깽이가 디폴트로 들어가는듯? 그래서 이거는 빼주려고
                if(localStorage.key(i) !== 'loglevel:webpack-dev-server'){
                    this.todoItems.push(localStorage.key(i));
                }
            }
        }
    }

}
</script>

리스트 완료처리

체크 표시를 클릭하면 완료처리를 하려고 한다. 우선 수정해야 할 부분이 TodoInput 컴포넌트다. 기존에는 localStorage.setItem(key,value) 에 그냥 string 값을 넣어줬는데 이제는 아래 코드처럼 객체를 넣어줄거다. completed 라는 멤버변수를 통해서 완료여부를 표시해줄거다.

    methods: {
        addTodo: function(){
            if(this.newTodoItem !== ''){
                //단순히 입력값을 넣는거 말고 체크 여부를 함꼐 포함한 객체를 넣어줄거다. -> todolist 컴포넌트 개발하면서 체크 여부가 추가가 됨
                var obj = {completed: false, item: this.newTodoItem}
                localStorage.setItem(this.newTodoItem,JSON.stringify(obj));//객체를 json을 string 으로 쭉 넣어주는듯
                this.clearInput();
            }
        },

그러고 나서 이제 TodoList 컴포넌트에서 created() 함수를 수정해줘야 한다.

    created:function(){
        //뷰 인스턴스가 생성되자마자 실행되는 lifecycle 훅
        if(localStorage.length > 0) {
            for(var i = 0 ; i<localStorage.length ; i++){
                if(localStorage.key(i) !== 'loglevel:webpack-dev-server'){
                    // this.todoItems.push(localStorage.key(i));
                    
                    //완료버튼 토글을 위해 JSON 형식으로 localStorage 에 저장해둔걸 꺼내서 todoItems 에 넣을거다
                    //이렇게 하면 	{"completed":false,"item":"aaa"} 이게 나오게 되고
                    //이걸 JSON.parse 하면 다시 객체 형식으로 todoItems에 push 됨
                    this.todoItems.push(JSON.parse(localStorage.getItem(localStorage.key(i))));
                }
            }
        }
    }

전체적으로 바뀐 코드를 보면 v-for 부분의 v-bind:key="todoItem.item" 으로 바꿔 줘야 하는데, v-bind:key에는 문자열 또는 숫자가 와야 한다고 한다. 또 하나는 v-bind:class="{xxxx}" 속성을 통해서 특정한 값에 따라서 css 를 적용 할 수 있다!!

<template>
  <div>
      <ul>
          <li v-for="(todoItem,index) in todoItems" v-bind:key="todoItem.item" class="shadow">
            <i class="checkBtn fas fa-check" v-bind:class="{checkBtnCompleted: todoItem.completed}" v-on:click="toggleComplete(todoItem,index)"></i>
            <span v-bind:class="{textCompleted: todoItem.completed}">{{todoItem.item}}</span>
            <span class="removeBtn" v-on:click="removeTodo(todoItem, index)">
                <i class="fas fa-trash-alt"></i>
            </span>
          </li>
      </ul>
  </div>
</template>  

<script>
export default {
    data: function(){
        return{
            todoItems:[],
        }
    },
    methods: {
        removeTodo : function(todoItem, index){
            localStorage.removeItem(todoItem);
            this.todoItems.splice(index,1);
        },
        toggleComplete : function(todoItem,index){
            todoItem.completed = !todoItem.completed;
            //로컬 스토리지 데이터 갱신
            localStorage.removeItem(todoItem.item);
            localStorage.setItem(todoItem.item, JSON.stringify(todoItem));
        }
    },
    created:function(){
        //뷰 인스턴스가 생성되자마자 실행되는 lifecycle 훅
        if(localStorage.length > 0) {
            for(var i = 0 ; i<localStorage.length ; i++){
                if(localStorage.key(i) !== 'loglevel:webpack-dev-server'){
                    // this.todoItems.push(localStorage.key(i));
                    
                    //완료버튼 토글을 위해 JSON 형식으로 localStorage 에 저장해둔걸 꺼내서 todoItems 에 넣을거다
                    //이렇게 하면 	{"completed":false,"item":"aaa"} 이게 나오게 되고 이걸 JSON.parse 하면 다시 객체 형식으로 todoItems에 push 됨
                    this.todoItems.push(JSON.parse(localStorage.getItem(localStorage.key(i))));
                }
            }
        }
    }

}
</script>

애플리케이션 구조 개선하기

기존 애플리케이션의 문제점은 컴포넌트간의 통신이 적용되어 있지 않아서 clear all 을 하거나 할일을 추가했을때 localStorage 에는 추가가 되지만 화면에 즉각적으로 반영이 되지 않았다.

App.vue 에서 이제 데이터를 관리하고 input & footer 컴포넌트에서는 emit 을 발생시켜서 데이터의 변경을 실행시켜줄거다.

  • 우선 기존에 로컬스토리지의 값들을 list 컴포넌트에서 todoItems 라는 배열에 넣어줬는데, 해당부분을 App.vue 로 옮겨주자.
  data : function(){
    return{
      todoItems: [],
    }
  },
  created:function(){
    //뷰 인스턴스가 생성되자마자 실행되는 lifecycle 훅
    if(localStorage.length > 0) {
        for(var i = 0 ; i<localStorage.length ; i++){
            if(localStorage.key(i) !== 'loglevel:webpack-dev-server'){
                // this.todoItems.push(localStorage.key(i));
                
                //완료버튼 토글을 위해 JSON 형식으로 localStorage 에 저장해둔걸 꺼내서 todoItems 에 넣을거다
                //이렇게 하면 	{"completed":false,"item":"aaa"} 이게 나오게 되고 이걸 JSON.parse 하면 다시 객체 형식으로 todoItems에 push 됨
                this.todoItems.push(JSON.parse(localStorage.getItem(localStorage.key(i))));
            }
        }
    }
  },

props 로 App.vue -> list 컴포넌트로 데이터 내려주기

그다음에 props 를 통해서 list컴포넌트로 todoItems 를 내려줄거다.
1. v-bind:내려보낼 프롭스 속성 이름="현재 위치의 컴포넌트 데이터 속성"
2. list 컴포넌트에 props 속성 추가후에 내려오는 문자열을 추가해주면 된다. 이후 v-for 부분에서 in propsdata 로 교체!

<template>
  <div id="app">
    <TodoHeader></TodoHeader>
    <TodoInput></TodoInput>
    <TodoList v-bind:propsdata="todoItems"></TodoList>
    <TodoFooter></TodoFooter>
  </div>
</template>
//TodoList 컴포넌트에 props 속성 추가
<script>
export default {
    props:[
        'propsdata'
    ],

emit 을 통해서 input 컴포넌트 -> App 컴포넌트 데이터 추가 및 삭제

v-on 을 통해서 이벤트를 매핑시킬 수 있다. input 컴포넌트에서 데이터 추가 시 addTodoItem 으로 이벤트가 올라오면 App.vue 에 있는 addOneItem 메서드가 실행이 된다.

<template>
  <div id="app">
    <TodoHeader></TodoHeader>
    <TodoInput v-on:addTodoItem="addOneItem"></TodoInput>
    <TodoList v-bind:propsdata="todoItems" v-on:removeItem="removeOneItem"></TodoList>
    <TodoFooter></TodoFooter>
  </div>
</template>

<script>
import TodoHeader from './components/TodoHeader.vue';
import TodoList from './components/TodoList.vue';
import TodoInput from './components/TodoInput.vue';
import TodoFooter from './components/TodoFooter.vue';

export default {
  data : function(){
    return{
      todoItems: [],
    }
  },
  methods:{
    addOneItem: function(todoItem){
      //input 컴포넌트에서 이벤트릴 발생시키면 addTodoItem 과 함께 todoItem 도 딸려서 올라온다.
      //이걸 활용해서 객체를 만들고 localStorage 에 넣어준다.
      //그리고 나서 App.vue 의 data() 영역에 있는 todoItems 에도 push 를 하게 되면 바로바로 화면이 갱신이 된다.
      var obj = {completed: false, item: todoItem}
      localStorage.setItem(todoItem,JSON.stringify(obj));//객체를 json을 string 으로 쭉 넣어주는듯
      this.todoItems.push(obj);
   },
   removeOneItem: function(todoItem,index){
     localStorage.removeItem(todoItem.item);
     this.todoItems.splice(index,1);
   }
  },
  created:function(){
    //뷰 인스턴스가 생성되자마자 실행되는 lifecycle 훅
    if(localStorage.length > 0) {
        for(var i = 0 ; i<localStorage.length ; i++){
            if(localStorage.key(i) !== 'loglevel:webpack-dev-server'){
                // this.todoItems.push(localStorage.key(i));
                
                //완료버튼 토글을 위해 JSON 형식으로 localStorage 에 저장해둔걸 꺼내서 todoItems 에 넣을거다
                //이렇게 하면 	{"completed":false,"item":"aaa"} 이게 나오게 되고 이걸 JSON.parse 하면 다시 객체 형식으로 todoItems에 push 됨
                this.todoItems.push(JSON.parse(localStorage.getItem(localStorage.key(i))));
            }
        }
    }
  },
  components:{
    'TodoHeader' : TodoHeader,
    'TodoInput' : TodoInput,
    'TodoList' : TodoList,
    'TodoFooter' : TodoFooter,
  }
  
}
</script>

App.vue를 컨테이너 컴포넌트의 역할을 한다고 한다. 나머지 컴포넌트들은 ui 적으로 표현만 해주는거고 컨테이너 컴포넌트는 특정 동작을 위해 데이터를 변경시켜주는 역할을 한다. 나중에 vuex 를 통해서 어떻게 바뀌는지 보면 좋을거 같다.


모달 컴포넌트 등록

아래와 같이 alert() 를 통해서 창을 생성해도 되지만 뭔가 이쁘지 않다. 이럴때 따로 컴포넌트를 등록해서 한번 만들어 보자.

우선 아래와 같이 모달 컴포넌트를 common 폴더 아래로 생성하자. 이후에 https://v2.vuejs.org/v2/examples/modal.html 사이트에서 html 과 css 를 복사해서 각각 template 과 style 에 넣어준다.

이후에 해당 컴포넌트를 input 컴포넌트에 추가해준다.

<script>
import AlertModal from './common/AlertModal.vue';

export default {
    data: function(){
        return {
            newTodoItem: "",
            showModal:false,
        }
    },
    methods: {
        addTodo: function(){
            if(this.newTodoItem !== ''){
                // this.$emit('이벤트 이름',인자1, 인자2,...); 이렇게 하면 App.vue 에서는 이벤트를 받게 된다.
                // 여기서 addTodoItem 이라는 이름으로 이벤트가 발생 된다.
                this.$emit('addTodoItem',this.newTodoItem)
                this.clearInput();
            }else{
                // alert('알일을 입력해주세요');
                this.showModal = !this.showModal;
            }
        },
        clearInput: function(){
            //입력 박스 초기화
            this.newTodoItem='';
        }
    },
    components:{
        AlertModal: AlertModal,
    }
}

그다음 template 부분에 아래를 추가해준다. 어떻게 사용하는지는 모달 컴포넌트 공식 페이지에 설명이 나와 있음 slot 부분을 잘 활용하면 나중에 개발할때 편하게 할 수 있을거 같음 하위 컴포넌트와 현재 컴포넌트를 연결시켜주는 도구라고 생각하면 될듯..!

    <!-- 모달창 -->
    <AlertModal v-if="showModal" @close="showModal = false">
    <!--
    you can use custom content here to overwrite
    default content-->
    <h3 slot="header">
        경고!!
        <i class="closeModalBtn fas fa-times" @click="showModal = false"></i>
    </h3>

    <div slot="body">
        null은 저장할 수 없습니다!!
    </div>
    </AlertModal>

트랜지션 적용

리스트에 목록을 추가하거나 삭제할때 부드럽게 하기를 원할 때, https://kr.vuejs.org/v2/guide/transitions.html 일단 여기 참고하고

목록을 뿌려주는 TodoList.vue 컴포는트 파일에다가 아래 스타일을 추가를 해주고 상단의 template 에서 ul 을 제거하고 transition-group 을 추가해준다. 그리고 거기다가 tag="ul" 을 설정해주자 그럼 부드럽게 목록들이 추가가 된다.

<template>
  <div>
		<!-- 트랜지션 적용 ul태그 대신에 transition-group 태그를 넣어주자 -->
		<transition-group name="list" tag="ul">
	  
		  <li v-for="(todoItem,index) in propsdata" v-bind:key="todoItem.item" class="shadow">
			<i class="checkBtn fas fa-check" v-bind:class="{checkBtnCompleted: todoItem.completed}" v-on:click="toggleComplete(todoItem,index)"></i>
			<span v-bind:class="{textCompleted: todoItem.completed}">{{todoItem.item}}</span>
			<span class="removeBtn" v-on:click="removeTodo(todoItem, index)">
				<i class="fas fa-trash-alt"></i>
			</span>
		  </li>
		</transition-group>
  </div>
</template>  
/* 뷰 트랜지션 */
.list-item {
  display: inline-block;
  margin-right: 10px;
}
.list-enter-active, .list-leave-active {
  transition: all 1s;
}
.list-enter, .list-leave-to /* .list-leave-active below version 2.1.8 */ {
  opacity: 0;
  transform: translateY(30px);
}


</style>

ES6 적용하기

  • const : 값을 한번 선언하고 할당하면 다시 값을 할당할 수 없다.

  • let : 똑같은 변수로 선언을 하지 못함 대신 값은 바꿀 수 있음

  • 화살표함수 : function(a,b){return a+b} 대신에 (a,b)=>{return a+b}

  • 속성 메서드의 축약 : 다음과 같이 축약해서 사용할 수 있다.

var obj = {
  	words: 100,
	foo : function(){}
}
var obj2 = {
  	words: 100,
	foo(){}
}
  • 객체의 속성명과 값 명이 동일할 때 아래와 같이 축약 가능
var figures = 10;
var dictionary = {
	//figures: figures,
    figures
};

Vuex 정리

  • State : 컴포넌트 간에 공유하는 데이터 : data()

  • View : 데이터를 표시하는 화면 template

  • Action : 사용자의 입력에 따라 데이터를 변경하는 methods

템플릿에서 어떤 버튼 (View)을 클릭했을 때 발생하는 addTodo 메서드가 발생 (Action) 하면 데이터(State)를 변경 이라고 생각하면 된다. Action이 발생하고 중간에 Mutations 메서드를 통해서 State를 바꾼다고 생각하자.

Vuex 설치

npm 명령어로 Vuex 를 설치해주고 아래 폴더와 같이 만들자 그리고 store.js 파일에 다음과 같이 작성. 이제 store를 다른 파일들에서도 사용할 수 있다. 그리고 나서 main.js 에 vuex 추가!

input 컴포넌트 Vuex state 적용하기

기존에는 TodoInput.vue 컴포넌트에서 emit 이 발생하면 App.vue 컨테이너 에서 addOneItem() 메서드가 실행되면서 로컬스토리지 및 data() 속성의 todoItems 에 값을 저장했다. 이제는 emit 을 발생시키는것이 아니라 store 에 commit 을 TodoInput.vue 컴포넌트에서 직접 해줄거다.

  • commit을 할 때는 첫번째 인자로 호출될 메서드 명을 적어주고, 이후에 필요한 매개변수들을 넣어준다.
<script>
import AlertModal from './common/AlertModal.vue';

export default {
    data(){
        return { 
            newTodoItem: "",
            showModal:false,
        }
    },
    methods: {
        addTodo(){
            if(this.newTodoItem !== ''){
                // this.$emit('addTodoItem',this.newTodoItem)
                //이제 추가할 때 emit 을 발생시키는게 아니라 sotre 로 commit 을 날릴거다. 
                this.$store.commit('addOneItem',this.newTodoItem.trim());
                this.clearInput();
            }else{
                // alert('알일을 입력해주세요');
                this.showModal = !this.showModal;
            }
        },

그리고 store.js 에서 addOneItem 을 추가해주는데, 여기서 첫번째 인자는 항상 state가 들어와야 한다. 이후에 필요한 매개변수들을 넣어주면 된다. 그리고 store 의 state 속성에 todoItems 를 배열로 가지고 있고 앞으로는 여기다가 값을 넣을거다.


export const store = new Vuex.Store({
  state:{
    todoItems: storage.fetch()//fetch 를 하면 로컬스토리지에 있는 값들을 담은 배열을 반환한다.
  },
  mutations:{
    addOneItem(state, todoItem){
      //es6를 적용하면서 const로 바꿔주자, let 을 써도 되지만 재할당이 안되는 const 가 디버깅하기 더 좋다.
      const obj = {completed: false, item: todoItem}
      localStorage.setItem(todoItem,JSON.stringify(obj));//객체를 json을 string 으로 쭉 넣어주는듯
      state.todoItems.push(obj);
    }
  }
});

마지막으로 App.vue 컨테이너의 템플릿에서도 v-on 도 지워준다. 어차피 Input 컴포넌트에서 emit 이 발생하지 않을거니까

<template>
  <div id="app">
    <TodoHeader></TodoHeader>
    <!-- <TodoInput v-on:addTodoItem="addOneItem"></TodoInput> 얘는 이제 필요 없어진다. -->
    <TodoInput></TodoInput>
    <TodoList v-bind:propsdata="todoItems" v-on:toggleItem="toggleOneItem"></TodoList>
    <TodoFooter v-on:clearAll="clearAllItems"></TodoFooter>
  </div>
</template>

이런식으로 vuex 를 이용해서 state 값으로 데이터를 관리하니까 App.vue 컨테이너 컴포넌트가 상당히 깔끔해진걸 볼 수 있다. 각각의 개별 컴포넌트에서 직접 mutation 을 호출해서 state를 변경하게 되니까 말이다.

근데 왜 mutations 를 통해서 변경하는 이유는 여러 컴포넌트에서 각각 해당 state를 변경한다고 하면 변경 추적이 어려운 점이 있다고 한다. (일리있군! 이렇게 하는게 유지보수 층면에서 좋은듯) State가 변경이 되면 vue 컴포넌트에 알아서 refresh() 같은걸 해주도록 하는듯

<template>
  <div id="app">
    <TodoHeader></TodoHeader>
    <!-- <TodoInput v-on:addTodoItem="addOneItem"></TodoInput> 얘는 이제 필요 없어진다. -->
    <TodoInput></TodoInput>
    <TodoList></TodoList>
    <TodoFooter></TodoFooter>
  </div>
</template>

<script>
import TodoHeader from './components/TodoHeader.vue';
import TodoList from './components/TodoList.vue';
import TodoInput from './components/TodoInput.vue';
import TodoFooter from './components/TodoFooter.vue';

export default {
  components:{
    TodoHeader,
    TodoInput,
    TodoList,
    TodoFooter,
  }
  
}
</script>

Actions 란?

  • 비동기 처리 로직을 선언하는 메서드. 비동기 로직을 담당하는 mutations

예를들어서 this.$store.dispatch('actions 호출메서드') 이런식으로 actions 를 호출하면 해당 actions 에 있는 메서드들 내부의 비동기 로직들을 처리한 후에 context.commit('mutations 호출 메서드') 을 하면 mutations 가 실행되고 state 의 데이터가 최종적으로 변경되는듯!!


Vuex 헬퍼함수

mapState & mapGetters

  • mapState : Vuex에 선언한 state 속성을 뷰 컴포넌트에 더쉽게 연결해주는 헬퍼

  • mapGetters : Vuex에 선언한 getters 속성을 뷰 컴포넌트에 더 쉽게 연결해주는 헬퍼


...문법 (Object Spread Operator)

아래와 같이 객체가 또다른 객체를 포함할때 다음과 같이 편리하게 사용하라고 만들어진 문법인듯 이걸 나중에 computed() 속성에 넣어줄때 기존에 다른 값들과 같이 mapState 와 mapGetters 를 쓸때 필요한 문법이라고 함

let josh = {
  field: 'web',
  language: 'js'
};

//기존
let developer = {
  nation: 'kor',
  field: josh.field,
  language: josh.language
};

//변경
let developer = {
  nation: 'kor',
  ...josh
};

mapMutations & mapActions

  • mapMutations : Vuex에 선언한 mutations 속성을 뷰 컴포넌트에 더 쉽게 연결해주는 헬퍼

  • mapActions : Vuex에 선언한 actions 속성을 뷰 컴포넌트에 더 쉽게 연결해주는 헬퍼


우선 mapGetters 를 적용하는 부분을 보면 store 부분에 getters 를 만들어 주고 이거를 사용할 컴포넌트에 가서 computed 속성에 넣어주면 된다. 이때 다른 computed 메서드들과 함꼐 사용할 수 있도록 ...문법을 사용해줘야 한다. 이후에는 해당 컴포는트 html 부분에서 this를 사용해서 storedTodoItems 에 접근할 수 있다. ['xxx',''yyy'...] 이런식으로 여러개를 넣을 수도 있다.

//store.js 의 getters 선언부분
export const store = new Vuex.Store({
  state:{
    todoItems: storage.fetch()
  },
  getters:{
    storedTodoItems(state){
      return state.todoItems;
    }
  },
  
  //TodoList.vue 컴포넌트 부분
 <li v-for="(todoItem,index) in this.storedTodoItems" v-bind:key="todoItem.item" class="shadow">
  
<script>
import {mapGetters, mapMutations } from 'vuex';

export default {                 
	computed:{
		...mapGetters(['storedTodoItems']),
	} 
}
</script>

mapMutations 가 적용되는걸 보면 우선 methods: 속성에 ...mapMutations() 를 등록해준다.

  • span 태그에서 사용하는 함수名 : '호출되는 뮤테이션名'

  • 호출하는 단에서 (여기서는 span 태그쪽) 넘겨주는 매개변수가 있다면 암묵적으로 mapMutations 에서 자동으로 넘겨준다.
    근데 보면 원래 mutation 호출할 때 객체로 넘겨줬으니까 html 쪽도 {} 객체형식으로 수정이 필요하다

<template>
  <div>
		<!-- 트랜지션 적용 ul태그 대신에 transition-group 태그를 넣어주자 -->
		<transition-group name="list" tag="ul">
	  
		  <li v-for="(todoItem,index) in this.storedTodoItems" v-bind:key="todoItem.item" class="shadow">
			<i class="checkBtn fas fa-check" v-bind:class="{checkBtnCompleted: todoItem.completed}" v-on:click="toggleComplete({todoItem,index})"></i>
			<span v-bind:class="{textCompleted: todoItem.completed}">{{todoItem.item}}</span>
			<span class="removeBtn" v-on:click="removeTodo({todoItem, index})">
				<i class="fas fa-trash-alt"></i>
			</span>
		  </li>
		</transition-group>
  </div>
</template>  

<script>
import {mapGetters, mapMutations } from 'vuex';

export default {
	methods: {
		...mapMutations({
			removeTodo : 'removeOneItem',
			toggleComplete : 'toggleOneItem'
		}),
		// removeTodo(todoItem,index){
		// 	//this.$emit('removeItem',todoItem,index);
		// 	this.$store.commit('removeOneItem',{todoItem,index});
		// },
		// toggleComplete(todoItem,index){
		// 	// this.$emit('toggleItem',todoItem,index);
		// 	this.$store.commit('toggleOneItem',{todoItem,index});
		// }
	},
	computed:{
		...mapGetters(['storedTodoItems']),
	} 
}
</script>
profile
내게 맞는 옷을 찾는중🔎

0개의 댓글