JaeWon's Devlog
article thumbnail
반응형

Vue + SpringBoot + Mysql 를 이용한 Todo 구현(1) - 프로젝트 구성하기

Vue + SpringBoot + Mysql 를 이용한 Todo 구현(2) - 프로젝트 환경 설정하기

Vue + SpringBoot + Mysql 를 이용한 Todo 구현(3) - Todo API 개발하기(1)

Vue + SpringBoot + Mysql 를 이용한 Todo 구현(4) - Todo API 개발하기(2)

Vue + SpringBoot + Mysql 를 이용한 Todo 구현(5) - Todo 화면 개발하기(1) - 컴포넌트 구성

Vue + SpringBoot + Mysql 를 이용한 Todo 구현(6) - Todo 화면 개발하기(2) - Vuex 적용


이전 글에서 Vue.js 에서 서버와 통신하여 데이터를 가져오는 것까지 확인하였습니다.

이번 글에서는 그 데이터를 각 컴포넌트에 가져와서 컴포넌트에서 해당 데이터를 관리하도록 화면 개발을 진행하도록 해보겠습니다.


6. UX 개편하기

- 필수적인 기능은 아니지만, 편의성을 위해 추가하였습니다.

6-1. 이름 저장하기(App.vue)

- 해당 사용자가 누구인지(물론 자기 자신일거지만...) 물어보는 화면을 추가합니다.

- 해당 화면은 처음 진입시에만 보이도록 설정하여, 보임/숨김 처리하도록 합니다.

- Vue.js 에서 v-if 를 사용하여 , 조건부 렌더링을 합니다.

- App.vue 파일의 HTML 소스를 수정합니다.

<!-- App.vue -->
<template>
  <div id="app">
    <div class="top">
      <TodoHeader />
      <div v-if="this.storedName">
        <TodoTitle />
        <TodoInput v-on:alertModal="showModal" @reload="reload"/>
      </div>
      <div v-else>
        <TodoHello />
      </div>
    </div>
    <div class="body">
      <div v-if="this.storedName">
        <TodoController />
        <TodoList ref="list" />
      </div>
      <TodoFooter />
    </div>
    <TodoModal v-show="modalVisible" v-on:close="modalVisible = false">
      <template v-slot:modal-text>{{ modalText }}</template>
    </TodoModal>
  </div>
</template>

- storedName(=userName)을 조회하여, 만약 존재하지 않는다면, <TodoHello />가 표시됩니다.

6-2. TodoHello.vue

- TodoHello.vue 컴포넌트는 이름을 물어보고, session에 저장합니다.

<!-- TodoHello.vue -->
<template>
    <div className="hello">
        <p className="hello__guide">
            <span className="hello__guide-text">Nice to meet you!</span>
            <span className="hello__guide-text">I’m going to remember your tasks.</span>
        </p>
        <p className="hello__ask">What is your name?</p>
        <label htmlFor="user-name">Name</label>
        <div className="main-input">
            <input
                className="hello__input"
                type="text"
                id="user-name"
                placeholder="Let me know your name"
                v-model="userName"
                v-on:keypress.enter="addUserName(userName)"
            />
            <button className="hello__button">
                <span className="blind">Enter</span>
            </button>
        </div>
    </div>
</template>

<script>
    import {mapMutations} from "vuex";
    export default {
        data() {
            return {
                userName: ""
            };
        },
        methods: {
            ...mapMutations({
                addUserName: "setUserName"
            })
        }
    };
</script>

<style>
	...
</style>

- Vuex 의 mutations 를 통해서 UserName 을 전달하여, Session에 저장합니다.

6-3. App.vue

- 6-1에서 HTML 소스를 정리하였다면, Script 도 정리합니다.

- 6-2에서 UserName을 저장하면서, Session 에 이름이 저장되어, 해당 값을 조회합니다.

// App.vue
<script>
import TodoHeader from "./components/TodoHeader";
import TodoTitle from "./components/TodoTitle";
import TodoInput from "./components/TodoInput";
import TodoController from "./components/TodoController";
import TodoList from "./components/TodoList";
import TodoFooter from "./components/TodoFooter";
import TodoHello from "./components/TodoHello";
import TodoModal from "./components/common/TodoModal";
import { mapGetters } from "vuex";
export default {
  name: "App",
  data() {
    return {
      modalVisible: false,
      modalText: ""
    };
  },
  computed: {
    ...mapGetters(["storedName"])
  },
  methods: {
    showModal(text) {
      this.modalText = text;
      this.modalVisible = !this.modalVisible;
    },
    reload(){
      this.$refs.list.getBoardList();
    }
  },
  components: {
    TodoHeader,
    TodoTitle,
    TodoInput,
    TodoController,
    TodoList,
    TodoFooter,
    TodoHello,
    TodoModal
  }
};
</script>

<style>
	...
</style>

- 두 작업이 진행되면, 아래 그림과 같이 동작합니다.

- 처음 화면을 진입하면, 이름을 묻고, 입력하게 되면, Todo 화면이 나오게 됩니다.

7. 화면 수정하기

7-1. TodoTitle.vue

- 시간대별 인사(시간대 별 Moring, Afternoon, evening)를 보여준다.

- 유저 이름(UserName)을 보여주고, 수정 가능케 한다.

- 오늘의 할 일(Todo)의 개수 현황을 보여준다.

<!-- TodoTitle.vue -->
<template>
  <div class="title">
    <p class="title__text">
      <span class="title__message">Good {{ message }},</span>
      <span
        v-on:keyup.enter="handleEnter"
        v-on:blur="handleBlur"
        class="title__name"
        ref="test"
        contenteditable="true"
      >{{ this.userName }}</span>
      .
    </p>
    <p class="title__task">
      <span class="title__task-top">오늘의 할 일은</span>
      <span class="title__task-count">
        <em class="title__task-left">{{ this.todoItemsCount.left }}</em>
        <em
          v-if="this.todoItemsCount.total"
          class="title__task-total"
        >&nbsp;/ {{ this.todoItemsCount.total }}</em>
      </span>
      <span class="title__task-bottom">
        <span v-if="this.todoItemsCount.total > 1"></span>
        <span v-else></span> 입니다 !
      </span>
      <span class="title__task-info"></span>
    </p>
  </div>
</template>

<script>
import getDate from "../assets/common/getDate.js";
export default {
  data() {
    return {
      //시간대별로 morning, afternoon, evening 출력
      message: "",
      // 저장된 userName 가져옴
      userName: this.$store.getters.storedName
    };
  },
  methods: {
    // 포커스아웃시, 입력된 이름을 userName으로 저장
    handleBlur(e) {
      const originalName = this.userName;
      const newName = e.target.innerText;
      if (newName !== originalName) {
        if (newName === "") {
          e.target.innerText = originalName;
        } else {
          this.$store.commit("setUserName", newName);
        }
      }
    },
    handleEnter() {
      this.$refs.test.blur();
    }
  },
  computed: {
    // 할 일 목록 수 표시
    todoItemsCount() {
      const checkLeftItems = () => {
        const items = this.$store.getters.storedTodoItems;
        let leftCount = 0;
        for (let i = 0; i < items.length; i++) {
          if (items[i].completed === false) {
            leftCount++;
          }
        }
        return leftCount;
      };
      const count = {
        total: this.$store.getters.storedTodoItemsCount,
        left: checkLeftItems()
      };
      return count;
    }
  },
  mounted() {
    this.message = getDate().daytime;
  }
};
</script>

- 해당 화면에서는 vuex를 사용하지 않고, 일반 vue.js가 제공하는 방법으로 데이터를 가져와 봤습니다.

- computed

  • this.$store.getters.storedTodoItems : Todo 목록의 아이템들을 가져온다.

- mounted()

  • getDate.js 에서 시간을 가져와 오전 오후 저녁에 대한 메시지를 보여준다.

7-2. TodoList.vue

- Todo 의 목록을 보여준다.

- 목록이 없을 경우, class를 추가하여 css 로 빈 화면을 보여준다.

<!-- TodoList.vue -->
<template>
  <transition-group name="list" tag="ul" class="list" v-bind:class="listempty">
    <li
      class="list__item"
      v-for="(todoItem, index) in this.storedTodoItems"
      v-bind:key="todoItem.item"
    >
      <input
        type="checkbox"
        v-bind:id="todoItem.item"
        v-bind:checked="todoItem.completed === true"
        v-on:change="toggleComplete({todoItem})"
      />
      <label v-bind:for="todoItem.item" class="list__label">
        <span class="icon-check"></span>
        <p class="list__text">{{ todoItem.item }}</p>
      </label>
      <div class="list__right">
        <button class="list__delete" v-on:click="removeTodo({todoItem, index})">
          <div class="blind">Delete</div>
        </button>
        <p class="list__date">{{ todoItem.date }}</p>
      </div>
    </li>
  </transition-group>
</template>

<script>
import {mapGetters} from "vuex";
import {mapMutations} from "vuex";

export default {
  name: 'todoItems',
  computed: {
    ...mapGetters(["storedTodoItems", "storedTodoItemsCount"]),
    listempty() {
      return this.storedTodoItemsCount <= 0 ? "list--empty" : null;
    }
  },
  methods: {
    ...mapMutations({
      removeTodo: "removeOneItem",
      toggleComplete: "toggleOneItem"
    }),
  },
};

</script>

<style>
	...
</style>

 

- 예를들어, vuex를 통해 데이터를 가져오는 순서는 다음과 같다.

1. TodoList.vue 에서 mapGetters 를 통해 storedTodoItems를 호출 
2. store.js 에서 getter를 조회 -> getter.js
3. getters.js 에서 호출한 storedTodoItems 조회

- coumputed

  • mapGetters : vuex를 통해 저장된 데이터를 가져온다.(storedTodoItems, storedTodoItemsCount)
  • listempty() : 목록이 없다면, list--empty class를 추가하여, 빈 화면에 대한 css 를 보여준다.

- methods

  • removeTodo : vuex를 통해 선택한 Todo 를 지운다.(removeOneItem)
  • toggleComplete : vuex를 통해 선택한 Todo 상태를 변경한다. (toggleOneItem)

7-3. TodoInput.vue

- Todo 아이템을 입력 받는다.

- 중복이 있을 경우 모달(Modal)창을 띄워 알린다.

<!-- TodoInput.vue -->
<template>
  <div class="add">
    <div class="main-input">
      <input
        type="text"
        class="add__input"
        placeholder="Enter your task here"
        v-model="newTodoItem"
        v-on:keypress.enter="addTodoItem"
        ref="taskInput"
      />
      <button class="add__buttonn" v-on:click="addTodoItem">
        <span class="blind">Add</span>
      </button>
    </div>
  </div>
</template>

<script>
import { mapGetters } from "vuex";
export default {
  data() {
    return {
      newTodoItem: ""
    };
  },
  computed: {
    ...mapGetters(["storedTodoItems", "storedTodoItemsCount"])
  },
  methods: {
    addTodoItem() {
      // 중복되는 내용인 경우
      const oldItems = this.storedTodoItems;
      for (let i = 0; i < this.storedTodoItemsCount; i++) {
        if (oldItems[i].item === this.newTodoItem) {
          const text = "I think you've already had the task.";
          this.$emit("alertModal", text);
          return false;
        }
      }
      // 빈 내용인 경우
      if (this.newTodoItem === "") {
        const text = "The form is empty. Please note your task.";
        this.$emit("alertModal", text);
        this.clearInput();
        return false;
      }
      this.$store.commit("addOneItem", this.newTodoItem);
      this.clearInput();
      this.$refs.taskInput.focus();
    },
    clearInput() {
      this.newTodoItem = "";
    }
  }
};
</script>

<style>
	...
</style>

- coumputed

  • mapGetters : vuex를 통해 저장된 데이터를 가져온다.(storedTodoItems, storedTodoItemsCount)

- methods

  • addTodoItem : vuex를 통해 입력한 Todo 를 추가한다.(addOneItem)
  • clearInput : Todo 를 입력 후에 입력 칸을 비워준다.

- 중복된 값이 있을 경우 this.$emit을 통해 모달창을 띄운다.

7-5. TodoModal.vue

- src/components/common/TodoModal.vue

<!-- TodoModal.vue -->
<template>
  <transition name="modal" appear>
    <div class="modal modal__dim" v-on:click.self="$emit('close')">
      <div class="modal__content">
        <p class="modal__text">
          <slot name="modal-text">(여기에 모달 내용이 들어가요)</slot>
        </p>
        <button class="modal__close" v-on:click="$emit('close')">OK</button>
      </div>
    </div>
  </transition>
</template>

<script>
export default {
};
</script>

<style>
	...
</style>

7-6. TodoController.vue

- Todo 목록을 정렬한다.

<!-- TodoController.vue -->
<template>
  <div class="controller">
    <div class="select">
      <label class="blind" for="order">Order</label>
      <select name="order" id="order" class="selectbox" v-model="selected" v-on:change="sortTodo">
        <option value="date-asc">Oldest</option>
        <option value="date-desc">Latest</option>
      </select>
    </div>
    <button class="clear" v-on:click="clearTodo">Clear All</button>
  </div>
</template>

<script>
import { mapMutations } from "vuex";
export default {
  data() {
    return {
      selected: "date-asc"
    };
  },
  methods: {
    ...mapMutations({
      clearTodo: "clearAllItem"
    }),
    sortTodo() {
      // 선택된 값에 따라 아이템 정렬
      if (this.selected === "date-desc") {
        this.$store.commit("sortTodoLatest");
      } else if (this.selected === "date-asc") {
        this.$store.commit("sortTodoOldest");
      }
    }
  }
};
</script>

<style>
	...
</style>

- 해당화면에서는 vuex와 일반 vue가 제공하는 기능을 통해 데이터를 관리한다.

- methods

  • clearTodo : vuex 를 통해 전체 Todo 아이템을 지운다.(clearAllItem)
  • sortTodo : vue가 제공하는 방법으로 Todo 아이템 목록을 정렬한다.(sortTodoLatest, sortTodoOlddest)

7-7. TodoHeader.vue

- 오늘 날짜를 보여준다.

<!-- TodoHeader.vue -->
<template>
  <header class="header">
    <h1 class="logo">
      <span class="blind"></span>
    </h1>
    <p class="header__date">{{ timestamp }}</p>
  </header>
</template>

<script>
import getDate from "../assets/common/getDate.js";
export default {
  data() {
    return {
      timestamp: ""
    };
  },
  created() {
    this.timestamp = `${getDate().month}/${getDate().date} ${getDate().week}`;
  }
};
</script>

<style>
	...
</style>

- getDate.js를 통해서 날짜를 조회하여 가져온다.

8. 완성

- 모든 개발이 완성되었습니다!!!

- 모든 전체 파일을 블로그상에서는 작성하지는 못 했습니다.(예를 들어, css 같은 거...) 전체 코드는 깃허브 참고 부탁드립니다.

9. 마무리

- 공부하고 이 내용을 블로그에 작성하려고 할 때는 금방 끝나겠지 했지만 생각보다 오래 걸렸다...

- 해당 글들을 작성하면서 여러 고민도 해보고, 조금 더 이해하고 정리할 수 있었던 것 같다. 

- 예전에는 jsp + spring 을 사용한 풀 스택 개발자가 많았지만, 최근 개발자 시장에서는 점점 프론트와 백엔드를 구분이 되어 개발자가 각각의 역할을 할 수 있도록 되어가고 있다.

- 작성자는 백엔드 개발자로서 길을 걸어가고 있지만, 한 번은 프론트엔드 언어를 경험해보고 이를 백엔드와 같이 사용하면서 간단하게 프로젝트를 진행하면 프론트 개발자의 입장에서도 고려하여 나중에 좋은 코드를 작성 할 수 있을 것이라고 생각하게 되어 진행하였다.

- 많은 프론트엔드 언어가 생기는 과정에서 그나마 접근이 편안 Vue.js를 선택하게 되었고, vue에 대해서는 아직 많이 부족한 부분이 많다.


참고

- https://www.inflearn.com/course/age-of-vuejs/dashboard

- https://nykim.work/76

반응형
profile

JaeWon's Devlog

@Wonol

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!