2020. 6. 14. 23:00

Vue.js로 To Do List 애플리케이션 만들기(하)

2020/06/13 - [Programming/.JS] - Vue.js로 To Do List 애플리케이션 만들기(상)

TodoList.vue


TodoList는 추가한 값들이 로컬스토리지에 저장되고 그 저장된 것을 가져와서 차례대로 뿌려줄 컴포넌트이다. 세션이나 DB에 저장한 뒤에 처리할 수도 있지만 간단하게 로컬스토리지를 통해서 이를 구현하도록 하자.

<template>
  <section>
    <transition-group name="list" tag="ul">
      <li v-for="(todoItem, index) in propsdata" v-bind:key="todoItem" class="shadow">
        <font-awesome-icon class="checkBtn" icon="check" aria-hidden="true" />
        {{ todoItem }}
        <span class="removeBtn" type="button" @click="removeTodo(todoItem, index)">
          <font-awesome-icon icon="trash" aria-hidden="true" />
        </span>
      </li>
    </transition-group>
  </section>
</template>

새로운 디렉티브가 등장했다. v-for는 지정된 갯수만큼 반복해서 HTML을 표시하는 반복문 디렉티브이다. 여기서 반복시킬 propsdata는 상위 컴포넌트로부터 전달받은 데이터를 이용할 것이다. 상위 컴포넌트인 App.vue에서 보내주지 않은 지금 상태에서는 propsdata가 로컬스토리지에서 가져온 데이터임을 가정하고 작성한다.

상위 컴포넌트에서 가져온 propsdata를 돌려서 나온 todoItem들을 <li>태그 안에 담을 것이다. {{}} 큰 대괄호는 HTML상에 변수 값, 즉 데이터를 표시하기 위한 표기 기호이다. propsdata에서 가져온 여러개의 데이터를 하나씩 todoItem으로 뽑아서 저 큰 대괄호안에 넣는다.

v-for 디렉티브를 사용할 때 key 속성을 반드시 지정해줘야한다. 뷰는 목록의 특정 아이템이 삭제되거나 추가 되었을 때, 돔에서 나머지 아이템의 순서를 다시 조정하지 않고 프레임워크 내부적으로 전체 아이템의 순서를 제어한다. 이렇게 프레임워크에서 목록 아이템의 순서를 제어하는 이유는 브라우저가 돔을 조작하는 데 소요되는 시간들을 최소화하기 위해서다.

간단하게 DB에서 인덱싱을 잡아주는 작업이라고 생각하면 편하다. 어차피 cli 2버전대는 오류가 나지 않지만 cli 3버전 이상 이 key 속성을 지정하지 않으면 오류를 띄운다.

상위 컴포넌트에서 가져온 데이터들과 함께 우측에는 휴지통 아이콘을 클릭했을 때는 removeTodo 메소드로 이동되도록 지정해줬다.

export default {
  props: ["propsdata"],
  methods: {
    removeTodo(todoItem, index) {
      this.$emit("removeTodo", todoItem, index);
    }
  }
};

이제 스크립트 부분으로 넘어오자. 상위 컴포넌트에 가져온 propsdata를 props속성에 넣고. 휴지통 아이콘을 클릭했을 때 실행되는 메소드인 removeTodo메소드를 만든다. 앞서 설명한 emit함수를 이용해 아이템의 값과 index 정보를 상위 컴포넌트로 보내게 된다. 상위 컴포넌트로 보내는 이유는 앞서 설명했기도 하지만 컴포넌트 내에서 처리하면 상태가 컴포넌트 내에서만 공유가 되기때문에 삭제버튼을 눌러도 바로 갱신이 안되는 경우가 생긴다.

즉각적으로 바로 컴포넌트에 반영하기 위해서는 모든 컴포넌트에 상태가 공유되어있어야 한다. 그래서 가장 상위에 존재하는 컴포넌트에 보내 이를 공유하기 위함이다.

<style scoped>
ul {
  list-style-type: none;
  padding-left: 0px;
  margin-top: 0;
  text-align: left;
}

li {
  display: flex;
  min-height: 50px;
  height: 50px;
  line-height: 50px;
  margin: 0.5rem 0;
  padding: 0 0.9rem;
  background: white;
  border-style: solid;
  border-color: #6478fb;
  border-radius: 5px;
  vertical-align: middle;
  align-items: center;
  justify-content: center;
}

.checkBtn {
  color: #62acde;
  margin-right: 5px;
}

.removeBtn {
  margin-left: auto;
  color: #de4343;
}

.list-enter-active,
.list-leave-active {
  transition: all 1s;
}
.list-enter,
.list-leave-to {
  opacity: 0;
  transform: translateY(30px);
}
</style>

앞서 나온 HTML 태그부분에 설명안한 부분이 있는데 바로 transition-group 태그이다. trasition은 뷰 애니메이션을 구현하기 위한 태그로 데이터 추가, 변경, 삭제에 대해서 페이드 인이나 페이드 아웃 등의 여러 가지 애니메이션 효과를 줄 수있다.

css에 등장한 list뒤로 붙은 enter-active와 leave-active, enter과 leave-to는 모두 이 애니메이션을 조정하는 트랜지션 클래스들이다. 이외에 여러가지 다얀한 트랜지션들은 아래 공식 가이드 문서를 참고하도록 하자.

https://kr.vuejs.org/v2/guide/transitions.html

 

진입/진출 그리고 리스트 트랜지션 — Vue.js

Vue.js - 프로그레시브 자바스크립트 프레임워크

kr.vuejs.org

 

TodoFooter.vue


Footer부분에는 휴지통 버튼을 눌러서 낱개로 지우는 것 뿐만이 아니라 버튼 한번에 모든 List들을 지우는 버튼을 구현할 것이다.

<template>
  <div class="clearAllContainer" @click="clearTodo">
    <span class="clearAllBtn">Clear All</span>
  </div>
</template>

Clear All버튼을 클릭하면 clearTodo메소드가 호출된다.

export default {
  methods: {
    clearTodo() {
      this.$emit("removeAll");
    }
  }
};

clearTodo는 역시 바로 즉각적으로 List가 지워진 것을 표시하기 위해 상위 컴포넌트로 'removeAll'이라는 이름으로 이벤트 신호를 보낸다.

<style scoped>
.clearAllContainer {
  width: 8.5rem;
  height: 50px;
  line-height: 50px;
  background-color: red;
  border-radius: 5px;
  margin: 0 auto;
  cursor: pointer;
}

.clearAllBtn {
  color: white;
  display: black;
}
</style>

Clear All버튼은 누르면 다 지워지니 경고의 의미로 빨간색으로 디자인해봤다. 취향에 따라 색상이나 디자인css 바꿔도 관계없다.

 

App.vue


대망에 모든 상태들이 모이는 최상위 App.vue 컴포넌트. 각 컴포넌트에서 모두 App.vue에서 만나!라고 말하고 헤어진 상태이기 때문에 각별히 유심히 지켜볼 필요가 있다.

특히 emit을 각 컴포넌트에서 보냈을 때 지은 이벤트명을 구분해서 상태를 처리해주도록 하자.

모두 App.vue에서 만나!!

<template>
  <div id="app">
    <TodoHeader></TodoHeader>
    <TodoInput v-on:addTodo="addTodo"></TodoInput>
    <TodoList v-bind:propsdata="todoItems" @removeTodo="removeTodo"></TodoList>
    <TodoFooter v-on:removeAll="clearAll"></TodoFooter>
  </div>
</template>

v-on은 앞서 설명한대로 emit으로 보내온 이벤트들을 감지해서 각자 해당하는 메소드들로 보낸다.

예를 들면 TodoInput 컴포넌트에서 addTodo 이름으로 호출되어진 것을 value라는 인자값을 가지고 addTodo메소드로 들어가게 된다. 이때 인자값은 메소드의 매게변수로 사용하게 된다.

v-bind는 말그대로 데이터와 HTML상의 요소를 묶어줍니다. 뒤이어 스크립트 부분에서 선언해줄 todoItems들을 propsdata에 묶어 담아서 보낼 것이다.

import TodoHeader from "./components/TodoHeader.vue";
import TodoInput from "./components/TodoInput.vue";
import TodoList from "./components/TodoList.vue";
import TodoFooter from "./components/TodoFooter.vue";

export default {
  data() {
    return {
      todoItems: []
    };
  },
  created() {
    if (localStorage.length > 0) {
      for (let i = 0; i < localStorage.length; i++) {
        if (localStorage.key(i) != "loglevel:webpack-dev-server") {
          this.todoItems.push(localStorage.key(i));
        }
      }
    }
  },
  methods: {
    addTodo(todoItem) {
      localStorage.setItem(todoItem, todoItem);
      this.todoItems.push(todoItem);
    },
    clearAll() {
      localStorage.clear();
      this.todoItems = [];
    },
    removeTodo(todoItem, index) {
      localStorage.removeItem(todoItem);
      this.todoItems.splice(index, 1);
    }
  },
  components: {
    TodoHeader,
    TodoInput,
    TodoList,
    TodoFooter
  }
};

하위 컴포넌트들을 등록해야하므로 지금까지 작업하던 모든 컴포넌트들을 Import한 뒤에 componets속성에 추가시켜준다.

나머지는 지금까지 컴포넌트를 작업하면서 해왔던 작업이지만 새롭게 보이는 인스턴스 속성이 있다.

바로 created()가 바로 그것인데, created는 처음 vue를 실행할 때 실행되는 부분을 명시한 것이다. 이는 애플리케이션의 라이프사이클에 해당되는 내용이고 페이지가 켜지고 표시되고 데이터가 마운트되고 사라지기까지의 주기를 정해놓고 그 순간에 실행하도록 순간 순간을 책갈피처럼 표시한 것이라고 보면된다.

https://kr.vuejs.org/v2/guide/instance.html#%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4-%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8

 

Vue 인스턴스 — Vue.js

Vue.js - 프로그레시브 자바스크립트 프레임워크

kr.vuejs.org

위 공식 문서를 보면 다이어그램으로 뷰의 라이프사이클을 확인할 수 있으니 다른 때에 원하는 코드를 넣기 위해서 숙지하면 좋다.

우리는 앱이 처음 생성되었을 때 로컬스토리지에 들어있는 내용들을 가져와 List에 뿌려줄 것이므로 created() 즉 앱이 시작될때 로컬스토리지에 내용이 있다면 가져와서 todoItems 배열에 넣도록 코드를 짰다.

  methods: {
    addTodo(todoItem) {
      localStorage.setItem(todoItem, todoItem);
      this.todoItems.push(todoItem);
    },
    clearAll() {
      localStorage.clear();
      this.todoItems = [];
    },
    removeTodo(todoItem, index) {
      localStorage.removeItem(todoItem);
      this.todoItems.splice(index, 1);
    }
  },

나머지는 emit으로 보내온 곳이 최종적으로 실행되는 부분이다. addTodo메소드는 Input부분에서 입력되어 로컬스토리지에 emit에 인자로 담겨온 값을 넣고, data에 선언 된 todoItems 배열에도 넣어준다.

clearAll메소드는 Footer에서 clear All 버튼을 누르면 넘어와서 로컬스토리지를 clear함수로 모두 비워주고 역시 data의 todoItems 또한 초기화 시켜준다.

removeTodo메소드는 List에서 휴지통 아이콘을 클릭했을 때 해당 값과 인덱스 정보를 매게변수로 해당하는 곳의 로컬스토리지를 제거하고 자바스크립트 배열에 사용하는 splice를 이용해 해당하는 순서의 배열을 제거했다.

<style lang="stylus">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

input {
  border-style: groove;
  width: 200px;
}

button {
  border-style: groove;
}

.shadow {
  box-shadow: 5px 10px 10px rgba(0, 0, 0, 0.03);
}
</style>

최종적으로 모든 컴포넌트에 공통적으로 적용되어야하는 CSS를 만들어 마무리 해주면 우리가 목표로하는 할일 관리 애플리케이션이 모두 완성이 된다.