본문 바로가기
GDSC/Vue 스터디

[Vue] 3차시 강의 정리

by 킴도비 2022. 7. 5.

25. json-server 패키지 설치

https://www.npmjs.com/package/json-server

 

json-server

Get a full fake REST API with zero coding in less than 30 seconds. Latest version: 0.17.0, last published: 8 months ago. Start using json-server in your project by running `npm i json-server`. There are 271 other projects in the npm registry using json-ser

www.npmjs.com

 

  • 오류 해결
 

VSCode 오류 : 이 시스템에서 스크립트를 실행할 수 없으므로 ...

VSCode 오류 : 이 시스템에서 스크립트를 실행할 수 없으므로... VSCode의 터미널을 통하여 npm혹은 yarn을 사용하여 처음 작업을 수행할 때, 다음과같은 에러가 발생할 수 있습니다. 내용은 "이 시스템

hellcoding.tistory.com

 

  • db.json
{
  "todos": [
    {
      "id": 1,
      "content" : "hi"
    },
    {
      "id": 2,
      "content" : "hi2"
    }
  ]
}

 

26. To-Do 생성시 db에 저장하기

https://www.npmjs.com/package/axios

 

axios

Promise based HTTP client for the browser and node.js. Latest version: 0.27.2, last published: a month ago. Start using axios in your project by running `npm i axios`. There are 78571 other projects in the npm registry using axios.

www.npmjs.com

 

  • 서버 돌릴 때 명령어 :
npm run serve

 

  • 데이터베이스 명령어 :
json-server --watch db.json

 

  • App.vue
<template>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!filterdTodos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="filterdTodos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />
  </div>
</template>

<script>
import { ref, computed} from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components : {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]);
    const error = ref('');

    const addTodo = (todo) => {
      // 데이터 베이스 투두를 저장
      error.value = '';
      axios.post('http://localhost:3000/todos', {
        subject: todo.subject,
        completed: todo.completed,
      }).then(res => { // 성공 시
      console.log(res);
        todos.value.push(res.data);    
      }).catch(err => {
        console.log(err);
        error.value = 'Something went wrong. ';
      });
    };

    const toggleTodo = (index) => {
      console.log(todos.value[index]); //변경되기 전
      todos.value[index].completed = !todos.value[index].completed; // 토글 : true -> false, false -> true로 변경해주는 소스
      console.log(todos.value[index]); //변경된 후
    };

    const deleteTodo = (index) => {
      todos.value.splice(index, 1);
    };

    const searchText = ref('');
    const filterdTodos = computed(() => {

      //필터가 된 Array를 넘김
      if(searchText.value) {
        return todos.value.filter(todo => {
          return todo.subject.includes(searchText.value);
        });   
      }

      // 아무것도 없을 시에는 원래 있던 todos를 출력해준다.
      return todos.value;
    });
 
    return {
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      filterdTodos,
      error,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

 

27. async vs sync

  • App.vue
<template>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!filterdTodos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="filterdTodos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />
  </div>
</template>

<script>
import { ref, computed} from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components : {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]);
    const error = ref('');

    const addTodo = (todo) => {
      // 데이터 베이스 투두를 저장
      error.value = '';
      console.log('start')
      axios.post('http://localhost:3000/todos', {
        subject: todo.subject,
        completed: todo.completed,
      }).then(res => { // 성공 시
      console.log(res);
        todos.value.push(res.data);    
      }).catch(err => {
        console.log(err);
        error.value = 'Something went wrong. ';
      });
      console.log('hello')
    };

    const toggleTodo = (index) => {
      console.log(todos.value[index]); //변경되기 전
      todos.value[index].completed = !todos.value[index].completed; // 토글 : true -> false, false -> true로 변경해주는 소스
      console.log(todos.value[index]); //변경된 후
    };

    const deleteTodo = (index) => {
      todos.value.splice(index, 1);
    };

    const searchText = ref('');
    const filterdTodos = computed(() => {

      //필터가 된 Array를 넘김
      if(searchText.value) {
        return todos.value.filter(todo => {
          return todo.subject.includes(searchText.value);
        });   
      }

      // 아무것도 없을 시에는 원래 있던 todos를 출력해준다.
      return todos.value;
    });
 
    return {
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      filterdTodos,
      error,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

28. async/await

  •  App.vue 소스 코드
<template>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!filterdTodos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="filterdTodos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />
  </div>
</template>

<script>
import { ref, computed} from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components : {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]);
    const error = ref('');

    const addTodo = async (todo) => {
      // 데이터 베이스 투두를 저장
      error.value = '';
      try{
        const res = await axios.post('http://localhost:3000/todos', {
          subject: todo.subject,
          completed: todo.completed,
        });
        todos.value.push(res.data);
      } catch(err){
        console.log(err);
        error.value = 'Somethig went wrong.';
      }
      
      // .then(res => { // 성공 시
      //   console.log(res);
      //   todos.value.push(res.data);    
      // }).catch(err => {
      //   console.log(err);
      //   error.value = 'Something went wrong. ';
      // });
      console.log('hello')
    };

    const toggleTodo = (index) => {
      console.log(todos.value[index]); //변경되기 전
      todos.value[index].completed = !todos.value[index].completed; // 토글 : true -> false, false -> true로 변경해주는 소스
      console.log(todos.value[index]); //변경된 후
    };

    const deleteTodo = (index) => {
      todos.value.splice(index, 1);
    };

    const searchText = ref('');
    const filterdTodos = computed(() => {

      //필터가 된 Array를 넘김
      if(searchText.value) {
        return todos.value.filter(todo => {
          return todo.subject.includes(searchText.value);
        });   
      }

      // 아무것도 없을 시에는 원래 있던 todos를 출력해준다.
      return todos.value;
    });
 
    return {
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      filterdTodos,
      error,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

29. To-Do 데이터 db에서 가져오기

  •  App.vue
<template>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!filterdTodos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="filterdTodos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />
  </div>
</template>

<script>
import { ref, computed} from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components : {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
    const error = ref('');

    const getTodos = async () => { // getTodos는 비동기
      try{
        // 결과가 올 때 까지 기다린다.
        const res = await axios.get('http://localhost:3000/todos') // 모든 todos 데이터를 요청함.    
        todos.value = res.data; // 결과
      } catch(err) {
        console.log(err); // 에러
        error.value = 'Somethig went wrong.';
      }
    };

    // 위 함수 실행 시킴
    getTodos();

    const addTodo = async (todo) => {
      // 데이터 베이스 투두를 저장
      error.value = '';
      try{
        const res = await axios.post('http://localhost:3000/todos', {
          subject: todo.subject,
          completed: todo.completed,
        });
        todos.value.push(res.data);
      } catch(err){
        console.log(err);
        error.value = 'Somethig went wrong.';
      }
      
      // .then(res => { // 성공 시
      //   console.log(res);
      //   todos.value.push(res.data);    
      // }).catch(err => {
      //   console.log(err);
      //   error.value = 'Something went wrong. ';
      // });
      console.log('hello')
    };

    const toggleTodo = (index) => {
      console.log(todos.value[index]); //변경되기 전
      todos.value[index].completed = !todos.value[index].completed; // 토글 : true -> false, false -> true로 변경해주는 소스
      console.log(todos.value[index]); //변경된 후
    };

    const deleteTodo = (index) => {
      todos.value.splice(index, 1);
    };

    const searchText = ref('');
    const filterdTodos = computed(() => {

      //필터가 된 Array를 넘김
      if(searchText.value) {
        return todos.value.filter(todo => {
          return todo.subject.includes(searchText.value);
        });   
      }

      // 아무것도 없을 시에는 원래 있던 todos를 출력해준다.
      return todos.value;
    });
 
    return {
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      filterdTodos,
      error,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
  • 문제점
    • 위의 코드만 사용하게 되면 초기 값을 빈 배열로 넣고 있기 때문에 실행 화면을 새로 고침 했을 시에 저장되어 있는 리스트들을 가져올 수 없으며 새 배열로 초기화 된다.

 

  • 해결법
    • db.json 데이터 요청 → axios → todos Array에 저장

 

  • 핵심 소스 코드
	const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
        const error = ref('');

        const getTodos = async () => { // getTodos는 비동기
          try{
            // 결과가 올 때 까지 기다린다.
            const res = await axios.get('http://localhost:3000/todos') // 모든 todos 데이터를 요청함.    
            todos.value = res.data; // 결과
          } catch(err) {
            console.log(err); // 에러
            error.value = 'Somethig went wrong.';
          }
        };

        // 위 함수 실행 시킴
        getTodos();

 

실행 화면

 

30. To-Do db에서 삭제하기

  • 데이터 삭제하는 법
    • DELETE /todos/삭제하고 싶은 번호

 

  • 핵심 소스 코드
const deleteTodo = async (index) => {
      // 인덱스를 가지고 있음. 
      // 아이디 찾기 -> todos.vaule에서 인덱스틀 통해서 todo를 찾아서 id를 가져와서 밑에 넣어줌.
      const id = todos.value[index].id;

      try {

        // delete 요청 보내기
        const res = await axios.delete('http://localhost:3000/todos/' + id);
        console.log(res);
        
      } catch (err) {

        console.log(err);
        error.value = 'Something went wrong. ';

      }
      
      todos.value.splice(index, 1); // 배열만 삭제
    };

 

  • 실행 화면

실행 화면 1

 

실행 화면 2

 

삭제된 데이터 확인

 

  • index 값을 알고 있기에 더 간단한 소스로 변경
const deleteTodo = async (index) => {
      error.value = ''; //오류 메세지 초기화
      const id = todos.value[index].id;

      try {

        await axios.delete('http://localhost:3000/todos/' + id);
        todos.value.splice(index, 1); // await 성공 시 실행 실패시 catch로 감

      } catch (err) {

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

 

  • App.vue 1
<template>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!filterdTodos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="filterdTodos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />
  </div>
</template>

<script>
import { ref, computed} from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components : {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
    const error = ref('');

    const getTodos = async () => { // getTodos는 비동기
      try{
        // 결과가 올 때 까지 기다린다.
        const res = await axios.get('http://localhost:3000/todos') // 모든 todos 데이터를 요청함.    
        todos.value = res.data; // 결과
      } catch(err) {
        console.log(err); // 에러
        error.value = 'Somethig went wrong.';
      }
    };

    // 위 함수 실행 시킴
    getTodos();

    const addTodo = async (todo) => {
      // 데이터 베이스 투두를 저장
      error.value = '';
      try{
        const res = await axios.post('http://localhost:3000/todos', {
          subject: todo.subject,
          completed: todo.completed,
        });
        todos.value.push(res.data);
      } catch(err){
        console.log(err);
        error.value = 'Somethig went wrong.';
      }
      
      // .then(res => { // 성공 시
      //   console.log(res);
      //   todos.value.push(res.data);    
      // }).catch(err => {
      //   console.log(err);
      //   error.value = 'Something went wrong. ';
      // });
      console.log('hello')
    };

    const toggleTodo = (index) => {
      console.log(todos.value[index]); //변경되기 전
      todos.value[index].completed = !todos.value[index].completed; // 토글 : true -> false, false -> true로 변경해주는 소스
      console.log(todos.value[index]); //변경된 후
    };

    const deleteTodo = async (index) => {
      // 인덱스를 가지고 있음. 
      // 아이디 찾기 -> todos.vaule에서 인덱스틀 통해서 todo를 찾아서 id를 가져와서 밑에 넣어줌.
      const id = todos.value[index].id;

      try {

        // delete 요청 보내기
        const res = await axios.delete('http://localhost:3000/todos/' + id);
        console.log(res);
        
      } catch (err) {

        console.log(err);
        error.value = 'Something went wrong. ';

      }
      
      todos.value.splice(index, 1); // 배열만 삭제
    };

    const searchText = ref('');
    const filterdTodos = computed(() => {

      //필터가 된 Array를 넘김
      if(searchText.value) {
        return todos.value.filter(todo => {
          return todo.subject.includes(searchText.value);
        });   
      }

      // 아무것도 없을 시에는 원래 있던 todos를 출력해준다.
      return todos.value;
    });
 
    return {
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      filterdTodos,
      error,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

  •  App.vue 2
<template>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!filterdTodos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="filterdTodos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />
  </div>
</template>

<script>
import { ref, computed} from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components : {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
    const error = ref('');

    const getTodos = async () => { // getTodos는 비동기
      try{
        // 결과가 올 때 까지 기다린다.
        const res = await axios.get('http://localhost:3000/todos') // 모든 todos 데이터를 요청함.    
        todos.value = res.data; // 결과
      } catch(err) {
        console.log(err); // 에러
        error.value = 'Somethig went wrong.';
      }
    };

    // 위 함수 실행 시킴
    getTodos();

    const addTodo = async (todo) => {
      // 데이터 베이스 투두를 저장
      error.value = '';
      try{
        const res = await axios.post('http://localhost:3000/todos', {
          subject: todo.subject,
          completed: todo.completed,
        });
        todos.value.push(res.data);
      } catch(err){
        console.log(err);
        error.value = 'Somethig went wrong.';
      }
      
      // .then(res => { // 성공 시
      //   console.log(res);
      //   todos.value.push(res.data);    
      // }).catch(err => {
      //   console.log(err);
      //   error.value = 'Something went wrong. ';
      // });
      console.log('hello')
    };

    const toggleTodo = (index) => {
      console.log(todos.value[index]); //변경되기 전
      todos.value[index].completed = !todos.value[index].completed; // 토글 : true -> false, false -> true로 변경해주는 소스
      console.log(todos.value[index]); //변경된 후
    };

    const deleteTodo = async (index) => {
      error.value = ''; //오류 메세지 초기화
      const id = todos.value[index].id;

      try {

        await axios.delete('http://localhost:3000/todos/' + id);
        todos.value.splice(index, 1); // await 성공 시 실행 실패시 catch로 감

      } catch (err) {

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const searchText = ref('');
    const filterdTodos = computed(() => {

      //필터가 된 Array를 넘김
      if(searchText.value) {
        return todos.value.filter(todo => {
          return todo.subject.includes(searchText.value);
        });   
      }

      // 아무것도 없을 시에는 원래 있던 todos를 출력해준다.
      return todos.value;
    });
 
    return {
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      filterdTodos,
      error,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

31. To-Do 토글 db에서 하기

  • To-Do 토글 작업 완료한 것 데이터베이스에 저장하기
  • PUT /todos/원하는 번호
    • 데이터 전체를 변경해줌.
  • PATCH /todos/원하는 번호
    • 부분적으로 변경해줌

 

  • 핵심 코드
const toggleTodo = async (index) => {
      error.value = '';
      const id = todos.value[index].id;

      try{
        await axios.patch('http://localhost:3000/todos/' + id, {
          completed: !todos.value[index].completed
        });    

        todos.value[index].completed = !todos.value[index].completed;

      } catch(err){

        console.log(err);
        error.value = 'Something went wrong. ';
        
      }
    };

 

  • TodoList.vue에서 “form-check-input” 클래스의 valuechecked로 변경해줘야 한다.

 

  • 실행 화면

실행 화면

 

데이터베이스 확인

 

  • App.vue
<template>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!filterdTodos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="filterdTodos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />
  </div>
</template>

<script>
import { ref, computed} from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components : {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
    const error = ref('');

    const getTodos = async () => { // getTodos는 비동기
      try{
        // 결과가 올 때 까지 기다린다.
        const res = await axios.get('http://localhost:3000/todos') // 모든 todos 데이터를 요청함.    
        todos.value = res.data; // 결과
      } catch(err) {
        console.log(err); // 에러
        error.value = 'Somethig went wrong.';
      }
    };

    // 위 함수 실행 시킴
    getTodos();

    const addTodo = async (todo) => {
      error.value = '';
      try{
        const res = await axios.post('http://localhost:3000/todos', {
          subject: todo.subject,
          completed: todo.completed,
        });
        todos.value.push(res.data);
      } catch(err){
        console.log(err);
        error.value = 'Somethig went wrong.';
      }
      
      console.log('hello')
    };

    const toggleTodo = async (index) => {
      error.value = '';
      const id = todos.value[index].id;

      try{
        await axios.patch('http://localhost:3000/todos/' + id, {
          completed: !todos.value[index].completed
        });    

        todos.value[index].completed = !todos.value[index].completed;

      } catch(err){

        console.log(err);
        error.value = 'Something went wrong. ';
        
      }
    };

    const deleteTodo = async (index) => {
      error.value = ''; //오류 메세지 초기화
      const id = todos.value[index].id;

      try {

        await axios.delete('http://localhost:3000/todos/' + id);
        todos.value.splice(index, 1); // await 성공 시 실행 실패시 catch로 감

      } catch (err) {

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const searchText = ref('');
    const filterdTodos = computed(() => {

      //필터가 된 Array를 넘김
      if(searchText.value) {
        return todos.value.filter(todo => {
          return todo.subject.includes(searchText.value);
        });   
      }

      // 아무것도 없을 시에는 원래 있던 todos를 출력해준다.
      return todos.value;
    });
 
    return {
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      filterdTodos,
      error,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

  • TodoList.vue
<template>
   <div 
      v-for="(todo, index) in todos"
      :key="todo.id"
      class="card mt-2"
    >
      <div class="card-body p-2 d-flex align-items-center">
        <div class="form-check flex-grow-1">
          <input 
            class="form-check-input" 
            type="checkbox"
            :checked="todo.completed"
            @change="toggleTodo(index)"
          >
          <label 
          class="form-check-label"
          :class="{ todo: todo.completed }"
          >
            {{ todo.subject }}
          </label>
        </div>
        <div>
          <button 
            class="btn btn-danger btn-sm"
            @click="deleteTodo(index)"
            >
            Delete
          </button>
        </div>
      </div> 
         
    </div>
</template>

<script>
export default {
    //데이터 형식 
    //props: ['todos']
    
    //Object 형식
    props: {
        todos: {
            type: Array,
            requeired: true
        }
    },

    empits: ['toggle-todo', 'delete-todo'],

    setup(props, { emit }) {
        const toggleTodo = (index) => {
            emit('toggle-todo', index);  // 'toggle-todo'는 이벤트 이름
        };

        const deleteTodo = (index) => {
            emit('delete-todo', index);        
        };

        return{
            toggleTodo,
            deleteTodo,
        }
    }

}
</script>

<style>

</style>

 

32. Pagination 1

  • App.vue
<template>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!filterdTodos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="filterdTodos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />

      <!-- Pagination -->
      <hr />
      <nav aria-label="Page navigation example">
        <ul class="pagination">
          <li class="page-item">
            <a class="page-link" href="#">Previous</a>
          </li>
          <li class="page-item">
            <a class="page-link" href="#">1</a>
          </li>
          <li class="page-item">
            <a class="page-link" href="#">2</a>
          </li>
          <li class="page-item">
            <a class="page-link" href="#">3</a>
          </li>
          <li class="page-item">
            <a class="page-link" href="#">Next</a>
          </li>
        </ul>
      </nav>


  </div>
</template>

<script>
import { ref, computed} from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components : {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
    const error = ref('');
    const totalPage = ref(0);
    const limit = 5;
    const page = ref(1);

    const getTodos = async () => { // getTodos는 비동기
      try{
        // 결과가 올 때 까지 기다린다.
        const res = await axios.get(
          `http://localhost:3000/todos?_page=${page.value}&_limit=${limit}` // '' 대신 ``를 쓰면 값을 넣어줄 수 있음.
        ); 
        totalPage.value = res.headers['x-total-count']; // headers에서 x- total-count를 받을 수 있음.
        todos.value = res.data; // 결과
      } catch(err) {
        console.log(err); // 에러
        error.value = 'Somethig went wrong.';
      }
    };

    // 위 함수 실행 시킴
    getTodos();

    const addTodo = async (todo) => {
      error.value = '';
      try{
        const res = await axios.post('http://localhost:3000/todos', {
          subject: todo.subject,
          completed: todo.completed,
        });
        todos.value.push(res.data);
      } catch(err){
        console.log(err);
        error.value = 'Somethig went wrong.';
      }
      
      console.log('hello')
    };

    const toggleTodo = async (index) => {
      error.value = '';
      const id = todos.value[index].id;

      try{
        await axios.patch('http://localhost:3000/todos/' + id, {
          completed: !todos.value[index].completed
        });    

        todos.value[index].completed = !todos.value[index].completed;

      } catch(err){

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const deleteTodo = async (index) => {
      error.value = ''; //오류 메세지 초기화
      const id = todos.value[index].id;

      try {

        await axios.delete('http://localhost:3000/todos/' + id);
        todos.value.splice(index, 1); // await 성공 시 실행 실패시 catch로 감

      } catch (err) {

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const searchText = ref('');
    const filterdTodos = computed(() => {

      //필터가 된 Array를 넘김
      if(searchText.value) {
        return todos.value.filter(todo => {
          return todo.subject.includes(searchText.value);
        });   
      }

      // 아무것도 없을 시에는 원래 있던 todos를 출력해준다.
      return todos.value;
    });
 
    return {
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      filterdTodos,
      error,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

실행 화면

 

33. Pagination 2

  • App.vue
<template>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!filterdTodos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="filterdTodos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />

      <!-- Pagination -->
      <hr />
      <nav aria-label="Page navigation example">
        <ul class="pagination">
          <li v-if="currentPage !== 1" class="page-item">
            <a class="page-link" href="#">Previous</a>
          </li>
          <li
            v-for="page in numberOfPages"
            :key="page"
            class="page-item"
            :class="currentPage === page ? 'actvie' : ''"
            >
            <a class="page-link" href="#">{{ page }}</a>
          </li>
          <li v-if="numberOfPages !== currentPage" class="page-item">
            <a class="page-link" href="#">Next</a>
          </li>
        </ul>
      </nav>

      {{ numberOfPages }}
  </div>
</template>

<script>
import { ref, computed} from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components : {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
    const error = ref('');
    const numberOfTodos = ref(0);
    const limit = 5;
    const currentPage = ref(1);

    const numberOfPages = computed(() => {
      return Math.ceil(numberOfTodos.value/limit);
    });

    const getTodos = async () => { // getTodos는 비동기
      try{
        // 결과가 올 때 까지 기다린다.
        const res = await axios.get(
          `http://localhost:3000/todos?_page=${currentPage.value}&_limit=${limit}` // '' 대신 ``를 쓰면 값을 넣어줄 수 있음.
        ); 
        numberOfTodos.value = res.headers['x-total-count']; // headers에서 x- total-count를 받을 수 있음.
        todos.value = res.data; // 결과
      } catch(err) {
        console.log(err); // 에러
        error.value = 'Somethig went wrong.';
      }
    };

    // 위 함수 실행 시킴
    getTodos();

    const addTodo = async (todo) => {
      error.value = '';
      try{
        const res = await axios.post('http://localhost:3000/todos', {
          subject: todo.subject,
          completed: todo.completed,
        });
        todos.value.push(res.data);
      } catch(err){
        console.log(err);
        error.value = 'Somethig went wrong.';
      }
      
      console.log('hello')
    };

    const toggleTodo = async (index) => {
      error.value = '';
      const id = todos.value[index].id;

      try{
        await axios.patch('http://localhost:3000/todos/' + id, {
          completed: !todos.value[index].completed
        });    

        todos.value[index].completed = !todos.value[index].completed;

      } catch(err){

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const deleteTodo = async (index) => {
      error.value = ''; //오류 메세지 초기화
      const id = todos.value[index].id;

      try {

        await axios.delete('http://localhost:3000/todos/' + id);
        todos.value.splice(index, 1); // await 성공 시 실행 실패시 catch로 감

      } catch (err) {

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const searchText = ref('');
    const filterdTodos = computed(() => {

      //필터가 된 Array를 넘김
      if(searchText.value) {
        return todos.value.filter(todo => {
          return todo.subject.includes(searchText.value);
        });   
      }

      // 아무것도 없을 시에는 원래 있던 todos를 출력해준다.
      return todos.value;
    });
 
    return {
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      filterdTodos,
      error,
      numberOfPages,
      currentPage,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

34. Pagination 3

  • App.vue
<template>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!filterdTodos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="filterdTodos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />

      <!-- Pagination -->
      <hr />
      <nav aria-label="Page navigation example">
        <ul class="pagination">
          <li v-if="currentPage !== 1" class="page-item">
            <a style="cursor: pointer" class="page-link" @click="getTodos(currentPage - 1)">
            Previous
            </a>
          </li>
          <li
            v-for="page in numberOfPages"
            :key="page"
            class="page-item"
            :class="currentPage === page ? 'actvie' : ''"
            >
            <a style="cursor: pointer" class="page-link" @click="getTodos(page)">{{ page }}</a>
          </li>
          <li v-if="numberOfPages !== currentPage" class="page-item">
            <a style="cursor: pointer" class="page-link"  @click="getTodos(currentPage + 1)">
            Next
            </a>
          </li>
        </ul>
      </nav>
  </div>
</template>

<script>
import { ref, computed} from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components : {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
    const error = ref('');
    const numberOfTodos = ref(0);
    const limit = 5;
    const currentPage = ref(1);

    const numberOfPages = computed(() => {
      return Math.ceil(numberOfTodos.value/limit);
    });

    const getTodos = async (page = currentPage.value) => { // getTodos는 비동기
      currentPage.value = page;
      try{
        // 결과가 올 때 까지 기다린다.
        const res = await axios.get(
          `http://localhost:3000/todos?_page=${page}&_limit=${limit}` // '' 대신 ``를 쓰면 값을 넣어줄 수 있음.
        ); 
        numberOfTodos.value = res.headers['x-total-count']; // headers에서 x- total-count를 받을 수 있음.
        todos.value = res.data; // 결과
      } catch(err) {
        console.log(err); // 에러
        error.value = 'Somethig went wrong.';
      }
    };

    // 위 함수 실행 시킴
    getTodos();

    const addTodo = async (todo) => {
      error.value = '';
      try{
        const res = await axios.post('http://localhost:3000/todos', {
          subject: todo.subject,
          completed: todo.completed,
        });
        todos.value.push(res.data);
      } catch(err){
        console.log(err);
        error.value = 'Somethig went wrong.';
      }
      
      console.log('hello')
    };

    const toggleTodo = async (index) => {
      error.value = '';
      const id = todos.value[index].id;

      try{
        await axios.patch('http://localhost:3000/todos/' + id, {
          completed: !todos.value[index].completed
        });    

        todos.value[index].completed = !todos.value[index].completed;

      } catch(err){

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const deleteTodo = async (index) => {
      error.value = ''; //오류 메세지 초기화
      const id = todos.value[index].id;

      try {

        await axios.delete('http://localhost:3000/todos/' + id);
        todos.value.splice(index, 1); // await 성공 시 실행 실패시 catch로 감

      } catch (err) {

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const searchText = ref('');
    const filterdTodos = computed(() => {

      //필터가 된 Array를 넘김
      if(searchText.value) {
        return todos.value.filter(todo => {
          return todo.subject.includes(searchText.value);
        });   
      }

      // 아무것도 없을 시에는 원래 있던 todos를 출력해준다.
      return todos.value;
    });
 
    return {
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      filterdTodos,
      error,
      numberOfPages,
      currentPage,
      getTodos,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

35. watch effect

  • App.vue
<template>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!filterdTodos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="filterdTodos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />

      <!-- Pagination -->
      <hr />
      <nav aria-label="Page navigation example">
        <ul class="pagination">
          <li v-if="currentPage !== 1" class="page-item">
            <a style="cursor: pointer" class="page-link" @click="getTodos(currentPage - 1)">
            Previous
            </a>
          </li>
          <li
            v-for="page in numberOfPages"
            :key="page"
            class="page-item"
            :class="currentPage === page ? 'actvie' : ''"
            >
            <a style="cursor: pointer" class="page-link" @click="getTodos(page)">{{ page }}</a>
          </li>
          <li v-if="numberOfPages !== currentPage" class="page-item">
            <a style="cursor: pointer" class="page-link"  @click="getTodos(currentPage + 1)">
            Next
            </a>
          </li>
        </ul>
      </nav>
  </div>
</template>

<script>
import { ref, computed, watchEffect } from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components : {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
    const error = ref('');
    const numberOfTodos = ref(0);
    let limit = 5;
    const currentPage = ref(1);

    // reactive state가 있고, 그 값이 변경이 되면 실행이 된다.
    watchEffect(() => {
      // console.log(currentPage.value); //1번 실행 됨. value 값이 바뀔 때마다 콘솔에 찍힘.
      // console.log(numberOfTodos.value);
      console.log(limit); // react하지 않으면 안찍힌다.
    })

    limit = 3;

    const numberOfPages = computed(() => {
      return Math.ceil(numberOfTodos.value/limit);
    });

    // const a = reactive({
    //   b: 1
    // });

    // watchEffect(() => {
    //   console.log(a.b);
    // });

    // a.b = 4;

    const getTodos = async (page = currentPage.value) => { // getTodos는 비동기
      currentPage.value = page;
      try{
        // 결과가 올 때 까지 기다린다.
        const res = await axios.get(
          `http://localhost:3000/todos?_page=${page}&_limit=${limit}` // '' 대신 ``를 쓰면 값을 넣어줄 수 있음.
        ); 
        numberOfTodos.value = res.headers['x-total-count']; // headers에서 x- total-count를 받을 수 있음.
        todos.value = res.data; // 결과
      } catch(err) {
        console.log(err); // 에러
        error.value = 'Somethig went wrong.';
      }
    };

    // 위 함수 실행 시킴
    getTodos();

    const addTodo = async (todo) => {
      error.value = '';
      try{
        const res = await axios.post('http://localhost:3000/todos', {
          subject: todo.subject,
          completed: todo.completed,
        });
        todos.value.push(res.data);
      } catch(err){
        console.log(err);
        error.value = 'Somethig went wrong.';
      }
      
      console.log('hello')
    };

    const toggleTodo = async (index) => {
      error.value = '';
      const id = todos.value[index].id;

      try{
        await axios.patch('http://localhost:3000/todos/' + id, {
          completed: !todos.value[index].completed
        });    

        todos.value[index].completed = !todos.value[index].completed;

      } catch(err){

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const deleteTodo = async (index) => {
      error.value = ''; //오류 메세지 초기화
      const id = todos.value[index].id;

      try {

        await axios.delete('http://localhost:3000/todos/' + id);
        todos.value.splice(index, 1); // await 성공 시 실행 실패시 catch로 감

      } catch (err) {

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const searchText = ref('');
    const filterdTodos = computed(() => {

      //필터가 된 Array를 넘김
      if(searchText.value) {
        return todos.value.filter(todo => {
          return todo.subject.includes(searchText.value);
        });   
      }

      // 아무것도 없을 시에는 원래 있던 todos를 출력해준다.
      return todos.value;
    });
 
    return {
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      filterdTodos,
      error,
      numberOfPages,
      currentPage,
      getTodos,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

  • TodoList.vue
<template>
   <div 
      v-for="(todo, index) in todos"
      :key="todo.id"
      class="card mt-2"
    >
      <div class="card-body p-2 d-flex align-items-center">
        <div class="form-check flex-grow-1">
          <input 
            class="form-check-input" 
            type="checkbox"
            :checked="todo.completed"
            @change="toggleTodo(index)"
          >
          <label 
          class="form-check-label"
          :class="{ todo: todo.completed }"
          >
            {{ todo.subject }}
          </label>
        </div>
        <div>
          <button 
            class="btn btn-danger btn-sm"
            @click="deleteTodo(index)"
            >
            Delete
          </button>
        </div>
      </div> 
         
    </div>
</template>

<script>
// import { watchEffect } from 'vue';
export default {
    //데이터 형식 
    //props: ['todos']
    
    //Object 형식
    props: {
        todos: {
            type: Array,
            requeired: true
        }
    },

    empits: ['toggle-todo', 'delete-todo'],

    setup(props, { emit }) {
      // watchEffect(() => {
      //   console.log(props.todos.length)
      // });
        const toggleTodo = (index) => {
            emit('toggle-todo', index);  // 'toggle-todo'는 이벤트 이름
        };

        const deleteTodo = (index) => {
            emit('delete-todo', index);        
        };

        return{
            toggleTodo,
            deleteTodo,
        }
    }

}
</script>

<style>

</style>

실행 화면

 

36. watch

  • App.vue
<template>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!filteredTodos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="filteredTodos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />

      <!-- Pagination -->
      <hr />
      <nav aria-label="Page navigation example">
        <ul class="pagination">
          <li v-if="currentPage !== 1" class="page-item">
            <a style="cursor: pointer" class="page-link" @click="getTodos(currentPage - 1)">
            Previous
            </a>
          </li>
          <li
            v-for="page in numberOfPages"
            :key="page"
            class="page-item"
            :class="currentPage === page ? 'actvie' : ''"
            >
            <a style="cursor: pointer" class="page-link" @click="getTodos(page)">{{ page }}</a>
          </li>
          <li v-if="numberOfPages !== currentPage" class="page-item">
            <a style="cursor: pointer" class="page-link"  @click="getTodos(currentPage + 1)">
            Next
            </a>
          </li>
        </ul>
      </nav>
  </div>
</template>

<script>
import { ref, computed, watch } from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components: {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
    const error = ref('');
    const numberOfTodos = ref(0);
    let limit = 5;
    const currentPage = ref(1);
    // const a = reactive({
    //   b: 1
    // })


    // const a = reactive({
    //   b: 1,
    //   c: 3
    // });

    // // reactive 사용
    // watch(() => [a.b, a.c], (current, prev) => {
    //   console.log(current, prev)
    // });

    // a.b = 2;

    // ref 사용
    watch([currentPage, numberOfTodos], (currentPage, prev) => {
      console.log(currentPage, prev)
    });

    limit = 3;

    const numberOfPages = computed(() => {
      return Math.ceil(numberOfTodos.value/limit);
    });

    // const a = reactive({
    //   b: 1
    // });

    // watchEffect(() => {
    //   console.log(a.b);
    // });

    // a.b = 4;

    const getTodos = async (page = currentPage.value) => { // getTodos는 비동기
      currentPage.value = page;
      try{
        // 결과가 올 때 까지 기다린다.
        const res = await axios.get(
          `http://localhost:3000/todos?_page=${page}&_limit=${limit}` // '' 대신 ``를 쓰면 값을 넣어줄 수 있음.
        ); 
        numberOfTodos.value = res.headers['x-total-count']; // headers에서 x- total-count를 받을 수 있음.
        todos.value = res.data; // 결과
      } catch(err) {
        console.log(err); // 에러
        error.value = 'Somethig went wrong.';
      }
    };

    // 위 함수 실행 시킴
    getTodos();

    const addTodo = async (todo) => {
      error.value = '';
      try{
        const res = await axios.post('http://localhost:3000/todos', {
          subject: todo.subject,
          completed: todo.completed,
        });
        todos.value.push(res.data);
      } catch(err){
        console.log(err);
        error.value = 'Somethig went wrong.';
      }
      
      console.log('hello')
    };

    const toggleTodo = async (index) => {
      error.value = '';
      const id = todos.value[index].id;

      try{
        await axios.patch('http://localhost:3000/todos/' + id, {
          completed: !todos.value[index].completed
        });    

        todos.value[index].completed = !todos.value[index].completed;

      } catch(err){

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const deleteTodo = async (index) => {
      error.value = ''; //오류 메세지 초기화
      const id = todos.value[index].id;

      try {

        await axios.delete('http://localhost:3000/todos/' + id);
        todos.value.splice(index, 1); // await 성공 시 실행 실패시 catch로 감

      } catch (err) {

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const searchText = ref('');
    const filteredTodos = computed(() => {

      //필터가 된 Array를 넘김
      if(searchText.value) {
        return todos.value.filter(todo => {
          return todo.subject.includes(searchText.value);
        });   
      }

      // 아무것도 없을 시에는 원래 있던 todos를 출력해준다.
      return todos.value;
    });
 
    return {
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      filteredTodos,
      error,
      numberOfPages,
      currentPage,
      getTodos,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

실행 화면

 

 

37. 검색 기능에 watch 적용하기

  • App.vue
<template>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!todos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="todos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />

      <!-- Pagination -->
      <hr />
      <nav aria-label="Page navigation example">
        <ul class="pagination">
          <li v-if="currentPage !== 1" class="page-item">
            <a style="cursor: pointer" class="page-link" @click="getTodos(currentPage - 1)">
            Previous
            </a>
          </li>
          <li
            v-for="page in numberOfPages"
            :key="page"
            class="page-item"
            :class="currentPage === page ? 'actvie' : ''"
            >
            <a style="cursor: pointer" class="page-link" @click="getTodos(page)">{{ page }}</a>
          </li>
          <li v-if="numberOfPages !== currentPage" class="page-item">
            <a style="cursor: pointer" class="page-link"  @click="getTodos(currentPage + 1)">
            Next
            </a>
          </li>
        </ul>
      </nav>
  </div>
</template>

<script>
import { ref, computed, watch } from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components: {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
    const error = ref('');
    const numberOfTodos = ref(0);
    let limit = 5;
    const currentPage = ref(1);
    const searchText = ref('');



    const numberOfPages = computed(() => {
      return Math.ceil(numberOfTodos.value/limit);
    });



    const getTodos = async (page = currentPage.value) => { // getTodos는 비동기
      currentPage.value = page;
      try{
        // 결과가 올 때 까지 기다린다.
        const res = await axios.get(
          `http://localhost:3000/todos?subject_like=${searchText.value}&_page=${page}&_limit=${limit}` // '' 대신 ``를 쓰면 값을 넣어줄 수 있음.
        ); 
        numberOfTodos.value = res.headers['x-total-count']; // headers에서 x- total-count를 받을 수 있음.
        todos.value = res.data; // 결과
      } catch(err) {
        console.log(err); // 에러
        error.value = 'Somethig went wrong.';
      }
    };

    // 위 함수 실행 시킴
    getTodos();

    const addTodo = async (todo) => {
      error.value = '';
      try{
        const res = await axios.post('http://localhost:3000/todos', {
          subject: todo.subject,
          completed: todo.completed,
        });
        todos.value.push(res.data);
      } catch(err){
        console.log(err);
        error.value = 'Somethig went wrong.';
      }
      
      console.log('hello')
    };

    const toggleTodo = async (index) => {
      error.value = '';
      const id = todos.value[index].id;

      try{
        await axios.patch('http://localhost:3000/todos/' + id, {
          completed: !todos.value[index].completed
        });    

        todos.value[index].completed = !todos.value[index].completed;

      } catch(err){

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const deleteTodo = async (index) => {
      error.value = ''; //오류 메세지 초기화
      const id = todos.value[index].id;

      try {

        await axios.delete('http://localhost:3000/todos/' + id);
        todos.value.splice(index, 1); // await 성공 시 실행 실패시 catch로 감

      } catch (err) {

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };


    watch(searchText, () => {
      getTodos(1);
      // console.log(current, prev);
    });
    // const filteredTodos = computed(() => {

    //   //필터가 된 Array를 넘김
    //   if(searchText.value) {
    //     return todos.value.filter(todo => {
    //       return todo.subject.includes(searchText.value);
    //     });   
    //   }

    //   // 아무것도 없을 시에는 원래 있던 todos를 출력해준다.
    //   return todos.value;
    // });
 
    return {
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      // filteredTodos,
      error,
      numberOfPages,
      currentPage,
      getTodos,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

38. 작은 문제점들 수정

  • App.vue
<template>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!todos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="todos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />

      <!-- Pagination -->
      <hr />
      <nav aria-label="Page navigation example">
        <ul class="pagination">
          <li v-if="currentPage !== 1" class="page-item">
            <a style="cursor: pointer" class="page-link" @click="getTodos(currentPage - 1)">
            Previous
            </a>
          </li>
          <li
            v-for="page in numberOfPages"
            :key="page"
            class="page-item"
            :class="currentPage === page ? 'actvie' : ''"
            >
            <a style="cursor: pointer" class="page-link" @click="getTodos(page)">{{ page }}</a>
          </li>
          <li v-if="numberOfPages !== currentPage" class="page-item">
            <a style="cursor: pointer" class="page-link"  @click="getTodos(currentPage + 1)">
            Next
            </a>
          </li>
        </ul>
      </nav>
  </div>
</template>

<script>
import { ref, computed, watch } from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components: {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
    const error = ref('');
    const numberOfTodos = ref(0);
    let limit = 5;
    const currentPage = ref(1);
    const searchText = ref('');



    const numberOfPages = computed(() => {
      return Math.ceil(numberOfTodos.value/limit);
    });



    const getTodos = async (page = currentPage.value) => { // getTodos는 비동기
      currentPage.value = page;
      try{
        // 결과가 올 때 까지 기다린다.
        const res = await axios.get(
          `http://localhost:3000/todos?_sort=id&_order=desc&subject_like=${searchText.value}&_page=${page}&_limit=${limit}` // '' 대신 ``를 쓰면 값을 넣어줄 수 있음.
        ); 
        numberOfTodos.value = res.headers['x-total-count']; // headers에서 x- total-count를 받을 수 있음.
        todos.value = res.data; // 결과
      } catch(err) {
        console.log(err); // 에러
        error.value = 'Somethig went wrong.';
      }
    };

    // 위 함수 실행 시킴
    getTodos();

    const addTodo = async (todo) => {
      error.value = '';
      try{
        await axios.post('http://localhost:3000/todos', {
          subject: todo.subject,
          completed: todo.completed,
        });

        getTodos(1);
        todos.value.push(res.data);
      } catch(err){
        console.log(err);
        error.value = 'Somethig went wrong.';
      }
      
      console.log('hello')
    };

    const toggleTodo = async (index) => {
      error.value = '';
      const id = todos.value[index].id;

      try{
        await axios.patch('http://localhost:3000/todos/' + id, {
          completed: !todos.value[index].completed
        });    

        todos.value[index].completed = !todos.value[index].completed;

      } catch(err){

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const deleteTodo = async (index) => {
      error.value = ''; //오류 메세지 초기화
      const id = todos.value[index].id;

      try {

        await axios.delete('http://localhost:3000/todos/' + id);
        
        getTodos(1);
      } catch (err) {

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };


    watch(searchText, () => {
      getTodos(1);
      // console.log(current, prev);
    });
    // const filteredTodos = computed(() => {

    //   //필터가 된 Array를 넘김
    //   if(searchText.value) {
    //     return todos.value.filter(todo => {
    //       return todo.subject.includes(searchText.value);
    //     });   
    //   }

    //   // 아무것도 없을 시에는 원래 있던 todos를 출력해준다.
    //   return todos.value;
    // });
 
    return {
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      // filteredTodos,
      error,
      numberOfPages,
      currentPage,
      getTodos,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

39. 검색 기능에 setTimeout 적용

  • App.vue
<template>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
        @keyup.enter="searchTodo"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!todos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="todos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />

      <!-- Pagination -->
      <hr />
      <nav aria-label="Page navigation example">
        <ul class="pagination">
          <li v-if="currentPage !== 1" class="page-item">
            <a style="cursor: pointer" class="page-link" @click="getTodos(currentPage - 1)">
            Previous
            </a>
          </li>
          <li
            v-for="page in numberOfPages"
            :key="page"
            class="page-item"
            :class="currentPage === page ? 'actvie' : ''"
            >
            <a style="cursor: pointer" class="page-link" @click="getTodos(page)">{{ page }}</a>
          </li>
          <li v-if="numberOfPages !== currentPage" class="page-item">
            <a style="cursor: pointer" class="page-link"  @click="getTodos(currentPage + 1)">
            Next
            </a>
          </li>
        </ul>
      </nav>
  </div>
</template>

<script>
import { ref, computed, watch } from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components: {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
    const error = ref('');
    const numberOfTodos = ref(0);
    let limit = 5;
    const currentPage = ref(1);
    const searchText = ref('');



    const numberOfPages = computed(() => {
      return Math.ceil(numberOfTodos.value/limit);
    });



    const getTodos = async (page = currentPage.value) => { // getTodos는 비동기
      currentPage.value = page;
      try{
        // 결과가 올 때 까지 기다린다.
        const res = await axios.get(
          `http://localhost:3000/todos?_sort=id&_order=desc&subject_like=${searchText.value}&_page=${page}&_limit=${limit}` // '' 대신 ``를 쓰면 값을 넣어줄 수 있음.
        ); 
        numberOfTodos.value = res.headers['x-total-count']; // headers에서 x- total-count를 받을 수 있음.
        todos.value = res.data; // 결과
      } catch(err) {
        console.log(err); // 에러
        error.value = 'Somethig went wrong.';
      }
    };

    // 위 함수 실행 시킴
    getTodos();

    const addTodo = async (todo) => {
      error.value = '';
      try{
        await axios.post('http://localhost:3000/todos', {
          subject: todo.subject,
          completed: todo.completed,
        });

        getTodos(1);
        todos.value.push(res.data);
      } catch(err){
        console.log(err);
        error.value = 'Somethig went wrong.';
      }
      
      console.log('hello')
    };

    const toggleTodo = async (index) => {
      error.value = '';
      const id = todos.value[index].id;

      try{
        await axios.patch('http://localhost:3000/todos/' + id, {
          completed: !todos.value[index].completed
        });    

        todos.value[index].completed = !todos.value[index].completed;

      } catch(err){

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const deleteTodo = async (index) => {
      error.value = ''; //오류 메세지 초기화
      const id = todos.value[index].id;

      try {

        await axios.delete('http://localhost:3000/todos/' + id);
        
        getTodos(1);
      } catch (err) {

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    let timeout = null;

    const searchTodo = () => {
      clearTimeout(timeout);
      getTodos(1);
    }

    watch(searchText, () => {
      clearTimeout(timeout); // 기존 2초 타임아웃 초기화
      timeout = setTimeout(()=> { // 2초 대기
        getTodos(1);
      }, 2000)
    });
 
    return {
      searchTodo,
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      // filteredTodos,
      error,
      numberOfPages,
      currentPage,
      getTodos,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

40. Vue Router 설치하기

  • vue router 설치. VS Code에서 터미널을 킨 뒤 아래 명령어를 입력해 준다.
npm install vue-router@4

 

 

API Reference | Vue Router

API Reference Props to Type: RouteLocationRawDetails:Denotes the target route of the link. When clicked, the value of the to prop will be passed to router.push() internally, so it can either be a string or a route location object. Home Home Home Home User

router.vuejs.org

 

  • src 밑에  pages와 router 폴더를 만들어 준다. 그리고 main.js 파일도 생성해준다. router 폴더 아래에는 index.js 파일을 생성해주고, pages 폴더 아래는 todos 폴더와 index.vue 파일을 안에 넣어준 것과 pages 안에 index.vue 파일을 하나 생성해준다.

 

  • pages/todos/index.vue 파일
<template>
  <div>Todos Page</div>
</template>

<script>
export default {

}
</script>

<style>

</style>

 

  • pages/index.vue
<template>
    <div>
        Home Page
    </div>
</template>

<script>
export default{

}
</script>

<style>

</style>

 

  • router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../pages/index.vue';
import Todos from '../pages/todos/index.vue';

const router = createRouter({
    history: createWebHistory(),
    routes: [
        {
            path: '/',
            name: 'Home',
            component: Home
        },
        {
            path: '/todos',
            name: 'Todos',
            component: Todos
        }
    ]
});

// 1 / home , 2 /todos , 3 /todos/create, 4 /todos/:id

export default router;

 

  • main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router';

createApp(App).use(router).mount('#app')

 

  • App.vue
<template>
<router-view/>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
        @keyup.enter="searchTodo"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!todos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="todos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />

      <!-- Pagination -->
      <hr />
      <nav aria-label="Page navigation example">
        <ul class="pagination">
          <li v-if="currentPage !== 1" class="page-item">
            <a style="cursor: pointer" class="page-link" @click="getTodos(currentPage - 1)">
            Previous
            </a>
          </li>
          <li
            v-for="page in numberOfPages"
            :key="page"
            class="page-item"
            :class="currentPage === page ? 'actvie' : ''"
            >
            <a style="cursor: pointer" class="page-link" @click="getTodos(page)">{{ page }}</a>
          </li>
          <li v-if="numberOfPages !== currentPage" class="page-item">
            <a style="cursor: pointer" class="page-link"  @click="getTodos(currentPage + 1)">
            Next
            </a>
          </li>
        </ul>
      </nav>
  </div>
</template>

<script>
import { ref, computed, watch } from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
import axios from 'axios';

export default {
  components: {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
    const error = ref('');
    const numberOfTodos = ref(0);
    let limit = 5;
    const currentPage = ref(1);
    const searchText = ref('');



    const numberOfPages = computed(() => {
      return Math.ceil(numberOfTodos.value/limit);
    });



    const getTodos = async (page = currentPage.value) => { // getTodos는 비동기
      currentPage.value = page;
      try{
        // 결과가 올 때 까지 기다린다.
        const res = await axios.get(
          `http://localhost:3000/todos?_sort=id&_order=desc&subject_like=${searchText.value}&_page=${page}&_limit=${limit}` // '' 대신 ``를 쓰면 값을 넣어줄 수 있음.
        ); 
        numberOfTodos.value = res.headers['x-total-count']; // headers에서 x- total-count를 받을 수 있음.
        todos.value = res.data; // 결과
      } catch(err) {
        console.log(err); // 에러
        error.value = 'Somethig went wrong.';
      }
    };

    // 위 함수 실행 시킴
    getTodos();

    const addTodo = async (todo) => {
      error.value = '';
      try{
        await axios.post('http://localhost:3000/todos', {
          subject: todo.subject,
          completed: todo.completed,
        });

        getTodos(1);
        todos.value.push(res.data);
      } catch(err){
        console.log(err);
        error.value = 'Somethig went wrong.';
      }
      
      console.log('hello')
    };

    const toggleTodo = async (index) => {
      error.value = '';
      const id = todos.value[index].id;

      try{
        await axios.patch('http://localhost:3000/todos/' + id, {
          completed: !todos.value[index].completed
        });    

        todos.value[index].completed = !todos.value[index].completed;

      } catch(err){

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const deleteTodo = async (index) => {
      error.value = ''; //오류 메세지 초기화
      const id = todos.value[index].id;

      try {

        await axios.delete('http://localhost:3000/todos/' + id);
        
        getTodos(1);
      } catch (err) {

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    let timeout = null;

    const searchTodo = () => {
      clearTimeout(timeout);
      getTodos(1);
    }

    watch(searchText, () => {
      clearTimeout(timeout); // 기존 2초 타임아웃 초기화
      timeout = setTimeout(()=> { // 2초 대기
        getTodos(1);
      }, 2000)
    });
 
    return {
      searchTodo,
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      // filteredTodos,
      error,
      numberOfPages,
      currentPage,
      getTodos,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

41. 지금까지 만든 To-Do 앱을 To-Do 페이지로 이동

  • App.vue
<template>
<router-view/>
</template>

<script>

export default {
 
}

</script>

<style>

</style>

 

  • pages/todos/index.vue
<template>
<router-view/>
  <div class="container">
    <h2>To-Do List</h2>
     <input
        class="form-control"
        type="text" 
        v-model="searchText"
        placeholder="Search"
        @keyup.enter="searchTodo"
      >
      <hr />
    <TodoSimpleForm @add-todo="addTodo" />
    <div style="color: red">
      {{ error }}
    </div>
 
    <div v-if="!todos.length">
      There is nothing to display
    </div>
    <TodoList 
      :todos="todos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />

      <!-- Pagination -->
      <hr />
      <nav aria-label="Page navigation example">
        <ul class="pagination">
          <li v-if="currentPage !== 1" class="page-item">
            <a style="cursor: pointer" class="page-link" @click="getTodos(currentPage - 1)">
            Previous
            </a>
          </li>
          <li
            v-for="page in numberOfPages"
            :key="page"
            class="page-item"
            :class="currentPage === page ? 'actvie' : ''"
            >
            <a style="cursor: pointer" class="page-link" @click="getTodos(page)">{{ page }}</a>
          </li>
          <li v-if="numberOfPages !== currentPage" class="page-item">
            <a style="cursor: pointer" class="page-link"  @click="getTodos(currentPage + 1)">
            Next
            </a>
          </li>
        </ul>
      </nav>
  </div>
</template>

<script>
// @/는 src를 뜻함
import { ref, computed, watch } from 'vue';
import TodoSimpleForm from '@/components/TodoSimpleForm.vue';
import TodoList from '@/components/TodoList.vue';
import axios from 'axios';

export default {
  components: {
    TodoSimpleForm,
    TodoList,
},

  setup() {
    const todos = ref([]); // 초기 값을 빈 배열로 넣고 있음.
    const error = ref('');
    const numberOfTodos = ref(0);
    let limit = 5;
    const currentPage = ref(1);
    const searchText = ref('');

    const numberOfPages = computed(() => {
      return Math.ceil(numberOfTodos.value/limit);
    });

    const getTodos = async (page = currentPage.value) => { // getTodos는 비동기
      currentPage.value = page;
      try{
        // 결과가 올 때 까지 기다린다.
        const res = await axios.get(
          `http://localhost:3000/todos?_sort=id&_order=desc&subject_like=${searchText.value}&_page=${page}&_limit=${limit}` // '' 대신 ``를 쓰면 값을 넣어줄 수 있음.
        ); 
        numberOfTodos.value = res.headers['x-total-count']; // headers에서 x- total-count를 받을 수 있음.
        todos.value = res.data; // 결과
      } catch(err) {
        console.log(err); // 에러
        error.value = 'Somethig went wrong.';
      }
    };

    // 위 함수 실행 시킴
    getTodos();

    const addTodo = async (todo) => {
      error.value = '';
      try{
        await axios.post('http://localhost:3000/todos', {
          subject: todo.subject,
          completed: todo.completed,
        });

        getTodos(1);
        // todos.value.push(res.data);
      } catch(err){
        console.log(err);
        error.value = 'Somethig went wrong.';
      }
      
      // console.log('hello')
    };

    const toggleTodo = async (index) => {
      error.value = '';
      const id = todos.value[index].id;

      try{
        await axios.patch('http://localhost:3000/todos/' + id, {
          completed: !todos.value[index].completed
        });    

        todos.value[index].completed = !todos.value[index].completed;

      } catch(err){

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    const deleteTodo = async (index) => {
      error.value = ''; //오류 메세지 초기화
      const id = todos.value[index].id;

      try {

        await axios.delete('http://localhost:3000/todos/' + id);
        
        getTodos(1);
      } catch (err) {

        console.log(err);
        error.value = 'Something went wrong. ';

      }
    };

    let timeout = null;

    const searchTodo = () => {
      clearTimeout(timeout);
      getTodos(1);
    }

    watch(searchText, () => {
      clearTimeout(timeout); // 기존 2초 타임아웃 초기화
      timeout = setTimeout(()=> { // 2초 대기
        getTodos(1);
      }, 2000)
    });
 
    return {
      searchTodo,
      todos,
      addTodo,
      toggleTodo,
      deleteTodo,
      searchText,
      // filteredTodos,
      error,
      numberOfPages,
      currentPage,
      getTodos,
    };
  }
}
</script>

<style>
  .todo {
    color: gray;
    text-decoration: line-through;
  }
</style>

 

42. Navbar 추가하기

 

Navbar

Documentation and examples for Bootstrap’s powerful, responsive navigation header, the navbar. Includes support for branding, navigation, and more, including support for our collapse plugin.

getbootstrap.com

 

  • App.vue
<template>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <a class="navbar-brand" href="#">GDSC Vue Study</a>

    <ul class="navbar-nav mr-auto">
      <li class="nav-item active">
        <a class="nav-link" href="#">Todos <span class="sr-only">(current)</span></a>
      </li>
    </ul>

</nav>
  <router-view/>
</template>

<script>

export default {
 
}

</script>

<style>

</style>

 

43. router-link

  • App.vue
<template>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <router-link class="navbar-brand" to="/">GDSC Vue Study</router-link>

    <ul class="navbar-nav mr-auto">
      <li class="nav-item active">
        <router-link class="nav-link" to="/todos">Todos </router-link>
      </li>
    </ul>

</nav>
  <router-view/>
</template>

<script>

export default {
 
}

</script>

<style>

</style>

 

44. vue router 작동 원리

  • a를 사용하는 것 보다 router를 사용했을 때 더 빠르게 값을 가져오는 것을 확인할 수 있다.

라우터 작동 원리

 

'GDSC > Vue 스터디' 카테고리의 다른 글

[Vue] 2차시 강의 정리  (0) 2022.05.21