Web dev/Vue

Vue로 todoList 만들기(옵션 API, 컴포지션 API)

도잎 2022. 12. 22. 21:58
반응형

vue의 state 흐름이 어떻게 이루어지 알아보기 위해 간단하게 CRUD를 구현할 수 있는 TodoList를 만들어봤다. 리액트는 useState를 통해 상태를 업데이트할 수 있었는데 vue와는 어떤 차이가 있을지도 궁금했다.

 

공식 문서를 보면 프로젝트를 시작하는 방법부터 자세하게 나와있어서 차근차근 따라해보았는데 생각보다 크게 어렵지는 않았던 것 같다.

 

1. Vue 프로젝트 시작하기

npm init vue@latest

 

해당 명령어를 입력하면 아래와 같이 옵션을 선택할 수 있는데, 내가 원하는 상황에 맞게 옵션을 선택해주면 빠르게 만들 수 있다. 나는 router 설정과 Eslint, prettier 옵션을 선택한 후 프로젝트를 생성했다.

 

✔ Project name: … <your-project-name>
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes

Scaffolding project in ./<your-project-name>...
Done.

 

프로젝트가 제대로 생성 되었다면 해당 프로젝트 폴더로 이동 후, 해당 프로젝트를 실행시킨다.

 

npm install
npm run dev

 

2. 옵션 API(Options API)

API 의 종류는 크게 두가지로 나뉘는 것 같았는데 옵션 api와 컴포지션 api이다. 먼저 options API에 대해서 살펴보면 각각의 옵션 별로 그룹화해놓은 것처럼 보였다. <template> 태그 안에는 화면에 보여줄 부분을 작성하고 <script> 태그 안에서는 함수나 데이터, 컴포넌트 등의 옵션에 해당하는 부분들을 작성하게 된다.

<template>
  <h2>{{ count }}</h2>
  <button @click="update">증가</button>
  <button @click="delete">감소</button>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    update() {
      this.count++
    },
    delete() {
      this.count--
    }
  },
}
</script>

 

위의 예제처럼 options API를 이용해 간단한 todoList를 구현해보았다. 처음 해보는 것이기 때문에 컴포넌트는 따로 나누지 않았다.

<template>
  <div class="todo">
    <h2>TodoList 만들기</h2>
      <div class="inputBox">
      <input type="text" placeholder="할 일을 입력하세요" :value="todoText" @input="typing"  @keyup.enter="addTodo"/>
      <button @click="addTodo">추가하기</button>
    </div>
    <div class="todoListBox">
      <div class="todoList" v-for="todo in todoList" :key="todo.id">
        <li :class="todo.isDone === false ? 'contents' : 'contentsDone'">{{ todo.content }}</li>
        <div>
          <button @click ="doneTodo(todo.id)">{{ todo.isDone === false ? '완료' : '취소'}}</button>
          <button @click ="deleteTodo(todo.id)">삭제</button>
        </div>
      </div>
    </div>
  </div>
</template>


<script>
export default {
  name: "Home",
  data() {
    return {
      todoList: [
      {
        id: 1,
        content: '마트 가기',
        isDone: false
      },
      {
        id: 2,
        content: '강아지 산책 시키기',
        isDone: false
      }
      ],
      todoText: ''
    };
  },
  methods: {
    addTodo() {
      if(this.todoText === '') {
        alert('할 일을 입력하세요!');
        return;
      }
      this.todoList = [...this.todoList, {id: this.todoList.length + 1, content: this.todoText, isDone: false}]
      this.todoText = ''
    },
    deleteTodo(id) {
      this.todoList = this.todoList.filter((ele) => ele.id !== id)
    },
    doneTodo(id) {
      console.log(id)
      this.todoList = this.todoList.map((todo) => todo.id === id ? {...todo, isDone: !todo.isDone} : todo)
    },
    typing(e) {
      this.todoText = e.target.value
    }
  },
}
</script>

 

3. 컴포지션 API(Composition API)

컴포지션 API는 리액트를 사용하는 것과 조금 비슷했던 것 같은데 기능별로 만들 때 보기 편할 것 같다는 생각이 들었다. Composition API는 <script setup> 스크립트 태그 안에 setup이라는 속성을 사용한다.

<script setup>
import { ref } from 'vue'

const count = ref(0)

function update() {
  count.value++
}

function delete() {
  count.value++
}

</script>

<template>
  <h2>{{ count }}</h2>
  <button @click="update">증가</button>
  <button @click="delete">감소</button>
</template>

 

컴포지션 API를 사용해서 구현한 TodoList는 다음과 같다. 리액트에서 사용하는 useRef와 vue에서 사용하는 ref의 개념은 살짝 다른 것 같았는데 여기선 state를 ref를 가지고 관리하는 것 같았다.

<script setup>
import { ref } from 'vue'

const todoList = ref([])
const todoText = ref('')

function addTodo() {
  if(todoText.value === "") {
    alert('내용을 입력하세요!')
    return;
  }
  todoList.value = [...todoList.value, {id: todoList.value.length + 1, content: todoText.value, isDone:false}]
  todoText.value = ""
}

function doneTodo(id) {
  todoList.value = todoList.value.map((todo) => todo.id === id ? {...todo, isDone: !todo.isDone} : todo)
}

function deleteTodo(id) {
  todoList.value = todoList.value.filter((ele) => ele.id !== id)
}

function typing(e) {
  todoText.value = e.target.value
}
</script>

<template>
  <div class="todo">
    <h2>TodoList 만들기</h2>
      <div class="inputBox">
      <input type="text" placeholder="할 일을 입력하세요" :value="todoText" @input="typing"  @keyup.enter="addTodo"/>
      <button @click="addTodo">추가하기</button>
    </div>
    <div class="todoListBox">
      <div class="todoList" v-for="todo in todoList" :key="todo.id">
        <li :class="todo.isDone === false ? 'contents' : 'contentsDone'">{{ todo.content }}</li>
        <div>
          <button @click ="doneTodo(todo.id)">{{ todo.isDone === false ? '완료' : '취소'}}</button>
          <button @click ="deleteTodo(todo.id)">삭제</button>
        </div>
      </div>
    </div>
  </div>
</template>

 

간단하게 todoList를 구현하며 리액트와의 차이를 조금 알게되었는데 ref뿐만 아니라 화면에 뿌려줄 때 map 대신 v-for 반복문을 사용하는 것이 일반적이라는 것을 알게되었다. 그렇다고 map을 쓰지 못하는 것은 아니었다.

반응형