티스토리 뷰
이번 시간에는 이전 포스팅에서 이야기했듯 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