티스토리 뷰

728x90

이번 시간에는 이전 포스팅에서 이야기했듯 API(Application Programming Interface) 연동하는 방식으로 변경한다.

 

 

API를 구성하기 위해서 json-server 모듈을 사용할 것이라 먼저 설치했다.

 

npm install -g json-server

 

db.json 파일을 만들고 데이터를 넣은 후

 

// db.json
{
  "todos": [{
    "id": "todo-1",
    "text": "API 연동하기",
    "completed": false
  }]
}

 

json-server를 실행하고 콘솔에 노출되는 URL로 접근하자.

 

json-server --watch db.json

 

nuxtjs/axios가 설치되어 있지 않다면 설치하자.

 

그리고 API 호출하는 코드를 작성했다.

service 폴더 안에 todos.js 파일을 생성한 후 CRUD 메서드를 만들었다. post, delete, put  RESTful 하게 동작하도록 되어 있어 사용하기 편했다.

 

// service/todos.js

import axios from 'axios'

const getTodos = async () => {
  try {
    const req = await axios.get('http://localhost:3000/todos')
    return req.data
  } catch (e) {
    // eslint-disable-next-line no-console
    console.error(`server error : ${e.error}`)
  }
}

const addTodo = async (todo) => {
  try {
    await axios.post('http://localhost:3000/todos', todo)
  } catch (e) {
    // eslint-disable-next-line no-console
    console.error(`server error : ${e.error}`)
  }
}

const updateTodo = async (todo) => {
  try {
    await axios.put(`http://localhost:3000/todos/${todo.id}`, todo)
  } catch (e) {
    // eslint-disable-next-line no-console
    console.error(`server error : ${e.error}`)
  }
}

const deleteTodo = async (id) => {
  try {
    await axios.delete(`http://localhost:3000/todos/${id}`)
  } catch (e) {
    // eslint-disable-next-line no-console
    console.error(`server error : ${e.error}`)
  }
}

export default {
  getTodos,
  addTodo,
  updateTodo,
  deleteTodo
}

 

store를 대대적으로 수정했다.

완료한 일을 일괄 삭제하는 기능을 구현해야 하는데 json-server에서 일괄 삭제가 기능을 제공하지 않기 때문에 삭제 API를 id 별로 수행하고 Promise.All 이후에 데이터를 다시 가져오도록 변경했다.

 

// store/index.js

import TodoService from '~/service/todos'

export const state = () => ({
  todos: [],
  visibility: 'all',
  selectedTodo: null
})

export const mutations = {
  FETCH_TODOS (state, todos) {
    state.todos = todos
  },
  UPDATE_VISIBILITY (state, visibility) {
    state.visibility = visibility
  },
  SET_EDIT_TODO (state, todo) {
    state.selectedTodo = todo
  }
}

export const actions = {
  async fetchTodo ({ commit }) {
    const todos = await TodoService.getTodos()
    commit('FETCH_TODOS', todos)
  },
  async createTodo ({ commit }, text) {
    await TodoService.addTodo({
      id: `todo-${Date.now()}`,
      text,
      completed: false
    })
  },
  async removeTodo ({ commit }, id) {
    await TodoService.deleteTodo(id)
    const todos = await TodoService.getTodos()
    commit('FETCH_TODOS', todos)
  },
  async editTodo ({ commit, state }, text) {
    await TodoService.updateTodo({
      ...state.selectedTodo,
      text
    })
    commit('SET_EDIT_TODO', null)
  },
  async toggleTodo ({ commit, state }, id) {
    const todo = state.todos.find(todo => todo.id === id)
    await TodoService.updateTodo({
      ...todo,
      completed: !todo.completed
    })

    const todos = await TodoService.getTodos()
    commit('FETCH_TODOS', todos)
  },
  removeCompletedTodo ({ commit, state }) {
    const completedTodos = state.todos.filter(todo => todo.completed)
    const promiseAll = completedTodos.map(todo => TodoService.deleteTodo(todo.id))

    Promise.all(promiseAll)
      .then(async () => {
        const todos = await TodoService.getTodos()
        commit('FETCH_TODOS', todos)
      })
  },
  updateVisibility ({ commit }, visibility) {
    commit('UPDATE_VISIBILITY', visibility)
  },
  setTodo ({ commit }, todo) {
    commit('SET_EDIT_TODO', todo)
  }
}
export const getters = {
  getFilteredTodos (state) {
    const { visibility } = state
    if (visibility === 'active') {
      return state.todos.filter(todo => !todo.completed)
    } else if (visibility === 'completed') {
      return state.todos.filter(todo => todo.completed)
    } else {
      return state.todos
    }
  },
  completedCount (state) {
    return state.todos.filter(todo => todo.completed).length
  }
}

 

TodoFooter 컴포넌트를 추가하여 필터 기능 및 완료 목록 일괄 삭제 기능을 추가하고 fetch 메서드에 비동기 데이터를 가져오도록 변경했다.

 

// pages/index.vue
  
<template>
  <div id="app">
    <todo-list />
    <todo-footer />
  </div>
</template>

<script>
import TodoList from '~/components/TodoList'
import TodoFooter from '~/components/TodoFooter'

export default {
  components: {
    TodoList,
    TodoFooter
  },
  async fetch ({ store }) {
    await store.dispatch('fetchTodo')
  }
}
</script>

 

TodoFooter는 Todo App 첫 번째 만들었던 코드를 그대로 가져왔다.

 

// components/TodoFooter.vue

<template>
  <div>
    <button type="button" @click="updateVisibility('all')">
      All
    </button>
    <button type="button" @click="updateVisibility('active')">
      Active
    </button>
    <button type="button" @click="updateVisibility('completed')">
      Completed
    </button>
    <button
      v-show="completedCount > 0"
      type="button"
      @click="removeCompletedTodo"
    >
      Clear completed
    </button>
  </div>
</template>

<script>
import { mapActions, mapGetters } from 'vuex'
export default {
  computed: {
    ...mapGetters(['completedCount'])
  },
  methods: {
    ...mapActions(['updateVisibility', 'removeCompletedTodo'])
  }
}
</script>

 

최종 폴더 구조와 화면은 다음과 같다. 목록 하단에 FooterComponent가 추가됐을 뿐 다른 화면은 기존과 동일하다.

 

 

마무리

데이터 동기화 시점을 컴포넌트의 beforeCreate 라이프사이클 시점에 했다. 이것도 정상 동작하긴 한다. 하지만 nuxt 문서를 읽어 보니 pages의 fetch 메서드가 더 맞다고 판단되어 변경했다. fetch 메서드 인자로 store 넘겨주고 있기 때문에 바로 dispatch를 바로 실행할 수 있다.

 

참고

https://github.com/typicode/json-server

 

코드

https://github.com/hyeonmi/nuxt-todos

 

 

728x90
댓글