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

[Vue] 2차시 강의 정리

by 킴도비 2022. 5. 21.

섹션 2. To-Do 리스트 만들기

08. 데이터 바인딩

  • <template> ... </template> 내에서 ref를 사용할 때는 .value 선언 없이 {{ 변수명 }} 사용해주면 된다.
<template>
  <div class="name">
    {{ name }} 
  </div>
  <button 
    class = "btn btn-primary"
    v-on:click="consoleLog"
  > 
  Click </button>

  <button
  class = "btn btn-primary"
  v-on:click="updateName"
  > 
  Update </button>

</template>

<script>
import { ref } from 'vue';
export default {
  setup(){
      const name = ref('GDSC');

      // const greeting = (name) => {
      //   return 'Hello, ' + name;
      // };

      // const greet = greeting(name);
      

      const consoleLog = () => {
        console.log('hello world');
      };

      const updateName = () => {
        name.value = ' Study';
      };

      return {
        name,
        consoleLog,
        updateName,
      };
  }
}
</script>

<style>
  .name{
    color: red;
  }

</style>

 

  • input box 사용
  • 바인딩: 연결하다
    • v-bind : 속성 = ”변수명”
    • <input type="text" v-bind:value="name">
    • v-bind가 없이 사용하게 되면 속성은 string 형식이 되어버린다.
    • input에 들어가는 속성들v-bind를 붙여 사용할 수 있다.
<template>
  <div v-bind:class="nameClass">
    {{ name }} 
  </div>

  <input v-bind:type="type" v-bind:value="name">

  <button
  class = "btn btn-primary"
  v-on:click="updateName"
  > 
  Update </button>

</template>

<script>
import { ref } from 'vue';
export default {
  setup(){
      const name = ref('GDSC');
      const type = ref('Number');
      const nameClass = ref('')

      const updateName = () => {
        name.value = ' Study';
        type.value = 'text';
        nameClass.value = 'name';
      };

      return {
        name,
        updateName,
        type,
        nameClass,
      };
  }
}
</script>

<style>
  .name{
    color: red;
  }

</style>

 

  • v-bind : 속성 형식으로 v-bind를 선언하는데 v-bind를 생략하고 :속성 형식으로도 사용이 가능하다
  • v-on: @로 대체가 가능하다
<template>
  <div v-bind:class="nameClass">
    {{ name }} 
  </div>

  <input :type="type" :value="name">

  <button
  class = "btn btn-primary"
  v-on:click="updateName"
  > 
  Update </button>

</template>

<script>
import { ref } from 'vue';
export default {
  setup(){
      const name = ref('GDSC');
      const type = ref('Number');
      const nameClass = ref('')

      const updateName = () => {
        name.value = ' Study';
        type.value = 'text';
        nameClass.value = 'name';
      };

      return {
        name,
        updateName,
        type,
        nameClass,
      };
  }
}
</script>

<style>
  .name{
    color: red;
  }

</style>

 

09. 양방향 바인딩 (v-model)

- 단방향

  • 단방향의 형식으로 할 시에는 화면에 있는 텍스트를 바뀌어도 소스코드에 있는 변수의 값은 변하지 않는다.
  • oninput Event : 유저가 무언가를 <input> 필드에 쓸 때 사용되는 이벤트이다.
  • e라는 객체는 inputbox에 대한 정보를 많이 가지고 있다
 

HTML oninput Event Attribute

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

 

HTML onclick Event Attribute

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

<template>

  <input type="text" :value="name">

  <button
  class = "btn btn-primary"
  v-on:click="onSubmit"
  > 
  Update </button>

</template>

<script>
import { ref } from 'vue';
export default {
  setup(){
      const name = ref('GDSC');


      const onSubmit = () => {
        console.log(name.value)
      };

      return {
        name,
        onSubmit,

      };
  }
}
</script>

<style>
  .name{
    color: red;
  }

</style>

 

 

- 양방향

  • 양방향 바인딩의 처리 순서
  1. name 값에 ref의 데이터 값을 가지고 있음.
  2. value에 v-bind 즉, 바인딩을 시킴. inputBox에 name 데이터 값이 들어가 있다
  3. input에 onclick 이벤트를 실행시켜주면 버튼 클릭시 updateName을 실행시킨다.
  4. updateName 함수 내에 있는 name.value를 변경해준다
  5. 선언된 name의 데이터 값이 변경되고, setup()에서 변경된 값을 사용할 수 있게 된다.
  •  
<template>

  <input 
    type="text" 
    :value="name"
    @input="upDateName"
  >

  <button
  class = "btn btn-primary"
  @click="onSubmit"
  > 
  Update </button>

</template>

<script>
import { ref } from 'vue';
export default {
  setup(){
      const name = ref('GDSC');


      const onSubmit = () => {
        console.log(name.value)
      };

      const upDateName = (e) => {
        //console.log(e.target.value) 
        //console.log(e)
        name.value = e.target.value;
      }

      return {
        name,
        onSubmit,
        upDateName,
      };
  }
}
</script>

<style>
  .name{
    color: red;
  }

</style>

 

  •  양방향 simple ver
<template>

  <input 
    type="text" 
    v-model="name"
  >

  <button
  class = "btn btn-primary"
  @click="onSubmit"
  > 
  Update </button>

</template>

<script>
import { ref } from 'vue';
export default {
  setup(){
      const name = ref('GDSC');


      const onSubmit = () => {
        console.log(name.value)
      };


      return {
        name,
        onSubmit,
      };
  }
}
</script>

<style>
  .name{
    color: red;
  }

</style>

 

10. 리스트에 새로운 To-Do 추가

  • refresh 막기
  • container 클래스는 화면 사이즈에 따라 여백을 주는 클래스이다.
  • form이 submit을 하게 되면 refresh을 하게 된다
  • refresh를 하는 법을 막는 법에는 두 가지 종류가 있는데 JavaScript에서는 아래와 같은 형식을 사용한다.
<form 
        @submit.prevent="onSubmit"
        class="d-flex"
        >

 

  • Event Modifiers 종류
    • .stop
    • .prevent
    • .capture
    • .self
    • .once
    • .passive
<template>
  <div class = "container">
      <h2>
        To-Do List
      </h2>
      <form 
        @submit.prevent="onSubmit"
        class="d-flex"
        >
        <div class="flex-grow-1 mr-2">
            <input 
              class="form-control"
              type="text" 
              v-model="todo"
              placeholder="Type new to-do"
            >      
        </div>

        <div>
          <button
            class = "btn btn-primary"
            type="submit"
            > 
            Add </button>
        </div>

      </form>
      {{ todos }}
  </div>

  

</template>

<script>
import { ref } from 'vue';
export default {
  setup(){
      const todo = ref('');
      const todos = ref([]);

      const onSubmit = () => {
        //e.preventDefault(); //refresh하는 속성을 안하게 만듬.
        todos.value.push({
          id: Date.now(), //TimeStamp
          subject: todo.value //제목    
        });
      };


      return {
        todo,
        onSubmit,
        todos,
      };
  }
}
</script>

<style>
  .name{
    color: red;
  }

</style>

 

11. To-Do 카드 만들기

<template>
  <div class = "container">
      <h2>
        To-Do List
      </h2>
      <form 
        @submit.prevent="onSubmit"
        class="d-flex"
        >
        <div class="flex-grow-1 mr-2">
            <input 
              class="form-control"
              type="text" 
              v-model="todo"
              placeholder="Type new to-do"
            >      
        </div>

        <div>
          <button
            class = "btn btn-primary"
            type="submit"
            > 
            Add </button>
        </div>

      </form>
      <div class="card mt-2">
        <div class ="card-body p-2">
          {{ todos[0].subject }}
        </div>
      </div>
      <div class="card mt-2">
        <div class ="card-body p-2">
          {{ todos[1].subject }}
        </div>
      </div>
  </div>

  

</template>

<script>
import { ref } from 'vue';
export default {
  setup(){
      const todo = ref('');
      const todos = ref([
        {id: 1, subject: '휴대폰 사기'},
        {id: 2, subject: '장보기'},
      ]);

      const onSubmit = () => {
        //e.preventDefault(); //refresh하는 속성을 안하게 만듬.
        todos.value.push({
          id: Date.now(), //TimeStamp
          subject: todo.value //제목    
        });
      };


      return {
        todo,
        onSubmit,
        todos,
      };
  }
}
</script>

<style>
  .name{
    color: red;
  }

</style>

 

12. v-for

  • v-for문을 사용하여 for 반복문을 사용할 수 있다.
  • v-for를 사용할 시에는 꼭 바인딩한 키 값(각각의 노드를 추적하기 위해 사용)을 넣어줘야 한다.
    • 사용방식
<div
        v-for="todo in todos" 
        class="card mt-2"
        :key="todo.id"
      >

 

  • Array 안에 데이터가 있으며 <template>안에서 반복시키고 싶으면 v-for을 사용해주면 된다.
  • ref를 사용한 todolist 데이터는 리엑티브하기 때문에 데이터 변경이 되면 todos의 데이터도 변경되며 렌더링 후에 추가된 것이 화면에서 확인할 수 있다.
<template>
  <div class = "container">

      <h2>
        To-Do List
      </h2>

      <form 
        @submit.prevent="onSubmit"
        class="d-flex"
        >

        <div class="flex-grow-1 mr-2">
            <input 
              class="form-control"
              type="text" 
              v-model="todo"
              placeholder="Type new to-do"
            >      
        </div>

        <div>
          <button
            class = "btn btn-primary"
            type="submit"
            > 
            Add </button>
        </div>
      </form>

      <!-- {{ todos }} -->

      <div
        v-for="todo in todos" 
        class="card mt-2"
        :key="todo.id"
      >

        <div class ="card-body p-2">
          {{ todo.subject }}
        </div>
      </div>

      <div class="card-body p-2">
        {{ todo.subject }}
      </div>

  </div>
</template>

<script>
import { ref } from 'vue';
export default {
  setup(){
      const todo = ref('');
      const todos = ref([
        {id: 1, subject: '휴대폰 사기'},
        {id: 2, subject: '장보기'},
      ]);

      const onSubmit = () => {
        //e.preventDefault(); //refresh하는 속성을 안하게 만듬.
        todos.value.push({
          id: Date.now(), //TimeStamp
          subject: todo.value //제목    
        });
      };


      return {
        todo,
        onSubmit,
        todos,
      };
  }
}
</script>

<style>

  .name{
    color: red;
  }

</style>

 

13. v-show vs v-if

[ v-show 사용법 ]

  • 초기 렌더 비용이 많이 듦. 두개의 렌더링을 다 해줘야 하기 때문에
    <div v-show="toggle">true</div>
    <div v-show="!toggle">false</div>

 

 

[ v-if ]

  • 일반적으로 토글하는데 비용이 많이 듦. 하나만 해주면 된다.
  • 토글을 자주 이용할 때
  • 조건이 런타임 동안에 자주 바뀌지 않을 때 사용해주면 된다.
<template>
  <!--  
    <div v-show="toggle">true</div>
    <div v-show="!toggle">false</div> 
    -->
  <div v-if="toggle">true</div>
  <div v-else>false</div>
  
  <button @click="onToggle">Toggle</button>
  <div class = "container">

  </div>
</template>

<script>
import { ref } from 'vue';
export default {
  setup(){
      const toggle = ref(false);
      const todo = ref('');
      const todos = ref([
        {id: 1, subject: '휴대폰 사기'},
        {id: 2, subject: '장보기'},
      ]);

      const onSubmit = () => {
        //e.preventDefault(); //refresh하는 속성을 안하게 만듬.
        todos.value.push({
          id: Date.now(), //TimeStamp
          subject: todo.value //제목    
        });
      };

      const onToggle =() =>{
        toggle.value = !toggle.value;
      };


      return {
        todo,
        onSubmit,
        todos,
        toggle,
        onToggle,
      };
  }
}
</script>

<style>

  .name{
    color: red;
  }

</style>

 

  • v-if로 empty인지 확인하고 출력하기
<template>
  <div class = "container">
    <h2>To-Do List</h2>
    <form @submit.prevent="onSubmit">
        <div class="d-flex">
          <div class="flex-grow-1 mr-2">
            <input
              class="form-control"
              type="text"
              v-model="todo"
              placeholder="Type new to-do">
          </div>

          <div>
            <button
              class="btn btn-primary"
              type="submit"
              >
                Add
              </button>
          </div>
        </div>     

        <div v-if="hasError" style="color: red">
          This field cannot be empty
        </div>
      </form>
      
      <div
        v-for="todo in todos"
        :key="todo.id"
        class="card mt-2"
        >
          <div class="card-body p-2">
            {{ todo.subject }}
          </div>          
        </div>
  </div>
</template>

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

export default {
  setup(){
      const todo = ref('');
      const todos = ref([
        {id: 1, subject: '휴대폰 사기'},
        {id: 2, subject: '장보기'},
      ]);

      const hasError = ref(false);

      const onSubmit = () => {
        if(todo.value === ''){
          hasError.value = true;
        } else {
            todos.value.push({
              id: Date.now(), //TimeStamp
              subject: todo.value //제목    
            }); 
            hasError.value = false;
         }
      };

      return {
        todo,
        onSubmit,
        todos,
        hasError,
      };
  }
}
</script>

<style>

  .name{
    color: red;
  }

</style>

 

14. 체크박스 바인딩

<template>
  <div class="container">
    <h2>To-Do List</h2>
    <form @submit.prevent="onSubmit">
      <div class="d-flex">
        <div class="flex-grow-1 mr-2">
          <input
            class="form-control"
            type="text" 
            v-model="todo"
            placeholder="Type new to-do"
          >
        </div>

        <div>
          <button 
            class="btn btn-primary"
            type="submit"
          >
            Add
          </button>
        </div>

      </div>

      <div v-show="hasError" style="color: red">
        This field cannot be empty
      </div>
    </form>

    <div 
      v-for="todo in todos"
      :key="todo.id"
      class="card mt-2"
    >
      <div class="card-body p-2">
        <div class="form-check">
          <input 
            class="form-check-input" 
            type="checkbox"
            v-model="todo.completed"
          >
          <label class="form-check-label">
            {{ todo.subject }}
          </label>
        </div>
      </div>
    </div>
  </div>
</template>

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

export default {
  setup() {
    const todo = ref('');
    const todos = ref([]);
    const hasError = ref(false);

    const onSubmit = () => {
      if (todo.value === '') {
        hasError.value = true;
      } else {
        todos.value.push({
          id: Date.now(),
          subject: todo.value,
          completed: true,
        });
        hasError.value = false;
        todo.value = ''; //empty로 바꿔줌
      }
    };

    return {
      todo,
      todos,
      onSubmit,
      hasError,
    };
  }
}
</script>

<style>
  .name {
    color: red;
  }
</style>

 

15. class/style 바인딩

[ style 바인딩 ]

<template>
  <div class="container">
    <h2>To-Do List</h2>
    <form @submit.prevent="onSubmit">
      <div class="d-flex">
        <div class="flex-grow-1 mr-2">
          <input
            class="form-control"
            type="text" 
            v-model="todo"
            placeholder="Type new to-do"
          >
        </div>

        <div>
          <button 
            class="btn btn-primary"
            type="submit"
          >
            Add
          </button>
        </div>

      </div>

      <div v-show="hasError" style="color: red">
        This field cannot be empty
      </div>
    </form>

    <div 
      v-for="todo in todos"
      :key="todo.id"
      class="card mt-2"
    >
      <div class="card-body p-2">
        <div class="form-check">
          <input 
            class="form-check-input" 
            type="checkbox"
            v-model="todo.completed"
          >
          <label 
          class="form-check-label"
          :style="todo.completed ? todoStyle : {}"
          >
            {{ todo.subject }}
          </label>
        </div>
      </div>
    </div>
  </div>
</template>

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

export default {
  setup() {
    const todo = ref('');
    const todos = ref([]);
    const hasError = ref(false);
    const todoStyle ={
      textDecoration: 'line-through',
      color: 'gray'
    };

    const onSubmit = () => {
      if (todo.value === '') {
        hasError.value = true;
      } else {
        todos.value.push({
          id: Date.now(),
          subject: todo.value,
          completed: false,
        });
        hasError.value = false;
        todo.value = ''; //empty로 바꿔줌
      }
    };

    return {
      todo,
      todos,
      onSubmit,
      hasError,
      todoStyle,
    };
  }
}
</script>

<style>
  .name {
    color: red;
  }
</style>

 

 

[ class 바인딩 ]

  • Object Syntax
    • Object안에 Key 값으로 넣고 싶은 클래스 네임을 쓰고 뒤에는 boolean으로 넣고 isActive가 true 값이라면 active가 들어가고, 아니라면 active가 추가되지 않는다.
    • active는 object, isActive는 boolean값이 들어간다.
<div :class="{ active: isActive }"></div>

 

  • 소스코드
<template>
  <div class="container">
    <h2>To-Do List</h2>
    <form @submit.prevent="onSubmit">
      <div class="d-flex">
        <div class="flex-grow-1 mr-2">
          <input
            class="form-control"
            type="text" 
            v-model="todo"
            placeholder="Type new to-do"
          >
        </div>

        <div>
          <button 
            class="btn btn-primary"
            type="submit"
          >
            Add
          </button>
        </div>

      </div>

      <div v-show="hasError" style="color: red">
        This field cannot be empty
      </div>
    </form>

    <div 
      v-for="todo in todos"
      :key="todo.id"
      class="card mt-2"
    >
      <div class="card-body p-2">
        <div class="form-check">
          <input 
            class="form-check-input" 
            type="checkbox"
            v-model="todo.completed"
          >
          <label 
          class="form-check-label"
          :class="{ todo: todo.completed }"
          >
            {{ todo.subject }}
          </label>
        </div>
      </div>
    </div>
  </div>
</template>

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

export default {
  setup() {
    const todo = ref('');
    const todos = ref([]);
    const hasError = ref(false);
    const todoStyle ={
      textDecoration: 'line-through',
      color: 'gray'
    };

    const onSubmit = () => {
      if (todo.value === '') {
        hasError.value = true;
      } else {
        todos.value.push({
          id: Date.now(),
          subject: todo.value,
          completed: false,
        });
        hasError.value = false;
        todo.value = ''; //empty로 바꿔줌
      }
    };

    return {
      todo,
      todos,
      onSubmit,
      hasError,
      todoStyle,
    };
  }
}
</script>

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

결과 화면

 

16. 배열에서 아이템 삭제

<template>
  <div class="container">
    <h2>To-Do List</h2>
    <form @submit.prevent="onSubmit">
      <div class="d-flex">
        <div class="flex-grow-1 mr-2">
          <input
            class="form-control"
            type="text" 
            v-model="todo"
            placeholder="Type new to-do"
          >
        </div>

        <div>
          <button 
            class="btn btn-primary"
            type="submit"
          >
            Add
          </button>
        </div>

      </div>

      <div v-show="hasError" style="color: red">
        This field cannot be empty
      </div>
    </form>
    <div v-if="!todos.length">
      추가된 Todo가 없습니다
    </div>

    <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"
            v-model="todo.completed"
          >
          <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>
  </div>
</template>

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

export default {
  setup() {
    const todo = ref('');
    const todos = ref([]);
    const hasError = ref(false);
    const todoStyle ={
      textDecoration: 'line-through',
      color: 'gray'
    };

    const onSubmit = () => {
      if (todo.value === '') {
        hasError.value = true;
      } else {
        todos.value.push({
          id: Date.now(),
          subject: todo.value,
          completed: false,
        });
        hasError.value = false;
        todo.value = ''; //empty로 바꿔줌
      }
    };

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

    return {
      todo,
      todos,
      onSubmit,
      hasError,
      todoStyle,
      deleteTodo,
    };
  }
}
</script>

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

 

17. 폼 컴포넌트 만들기

  • 컴포넌트를 사용하는 이유? 소스가 점점 길어지면서 관리하기 힘들어지기 때문에 따로 빼주어 관리한다.
  • /src/components 에서 기본 파일 삭제하고 TodoSimpleForm.vue 생성 해줌.
  • App.vue 소스코드
<template>
  <div class="container">
    <h2>To-Do List</h2>
    <TodoSimpleForm />
 
    <div v-if="!todos.length">
      추가된 Todo가 없습니다
    </div>

    <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"
            v-model="todo.completed"
          >
          <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>
  </div>
</template>

<script>
import { ref } from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';

export default {
  components : {
    TodoSimpleForm,
},

  setup() {
    const todo = ref('');
    const todos = ref([]);
    const hasError = ref(false);
    const todoStyle ={
      textDecoration: 'line-through',
      color: 'gray'
    };

    const onSubmit = () => {
      if (todo.value === '') {
        hasError.value = true;
      } else {
        todos.value.push({
          id: Date.now(),
          subject: todo.value,
          completed: false,
        });
        hasError.value = false;
        todo.value = ''; //empty로 바꿔줌
      }
    };

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

    return {
      todo,
      todos,
      onSubmit,
      hasError,
      todoStyle,
      deleteTodo,
    };
  }
}
</script>

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

 

  • TodoSimpleForm.vue 소스 코드
<template>
       <form @submit.prevent="onSubmit">
      <div class="d-flex">
        <div class="flex-grow-1 mr-2">
          <input
            class="form-control"
            type="text" 
            v-model="todo"
            placeholder="Type new to-do"
          >
        </div>

        <div>
          <button 
            class="btn btn-primary"
            type="submit"
          >
            Add
          </button>
        </div>

      </div>

      <div v-show="hasError" style="color: red">
        This field cannot be empty
      </div>
    </form>
</template>

<script>

</script>
    export default {}
<style>



</style>

 

18. emit

  • App.vue는 부모 컴포넌트. TodoSimpleForm.vue는 자식 컴포넌트
  • emit()을 통해 자식 컴포넌트에서 부모 컴포넌트로 보낸다.
  • App.vue 소스 코드
<template>
  <div class="container">
    <h2>To-Do List</h2>
    <TodoSimpleForm @add-todo="addTodo" />
 
    <div v-if="!todos.length">
      추가된 Todo가 없습니다
    </div>

    <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"
            v-model="todo.completed"
          >
          <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>
  </div>
</template>

<script>
import { ref } from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';

export default {
  components : {
    TodoSimpleForm,
},

  setup() {
    const todos = ref([]);
    const todoStyle ={
      textDecoration: 'line-through',
      color: 'gray'
    };

    const addTodo = (todo) => {
      console.log(todo);
      //자식이 보낸 데이터 확인
      todos.value.push(todo);
    };

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

    return {
      todos,
      addTodo,
      todoStyle,
      deleteTodo,
    };
  }
}
</script>

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

 

 

GitHub - kossiecoder/vue3-basic

Contribute to kossiecoder/vue3-basic development by creating an account on GitHub.

github.com

 

19. props

  • 자식 컴포넌트에서 부모 컴포넌트로 데이터를 보낼 때 props로 데이터를 보낸다
  • 사용 법
<Template>
	... 중략 ...
	<TodoList :todos="todos"/>
	<!-- 자식 컴포넌트 :원하는이름="데이터" -->
</Template>

 

  • props의 여러 타입

 

  • App.vue 소스 코드
<template>
  <div class="container">
    <h2>To-Do List</h2>
    <TodoSimpleForm @add-todo="addTodo" />
    
    <div v-if="!todos.length">
      추가된 Todo가 없습니다
    </div>
    <TodoList :todos="todos" />
  </div>
</template>

<script>
import { ref } from 'vue';
import TodoSimpleForm from './components/TodoSimpleForm.vue';
import TodoList from './components/TodoList.vue';
export default {
  components: {
    TodoSimpleForm,
    TodoList,
  },
  setup() {
    const todos = ref([]);
    const addTodo = (todo) => {
      todos.value.push(todo);
    };
    const deleteTodo = (index) => {
      todos.value.splice(index, 1);
    };
    return {
      todos,
      addTodo,
      deleteTodo,
    };
  }
}
</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"
            v-model="todo.completed"
          >
          <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
        }
    }

}
</script>

<style>

</style>

 

  • TodoSimpleForm.vue 소스 코드
<template>
    <form @submit.prevent="onSubmit">
      <div class="d-flex">
        <div class="flex-grow-1 mr-2">
          <input
            class="form-control"
            type="text" 
            v-model="todo"
            placeholder="Type new to-do"
          >
        </div>

        <div>
          <button 
            class="btn btn-primary"
            type="submit"
          >
            Add
          </button>
        </div>

      </div>

      <div v-show="hasError" style="color: red">
        This field cannot be empty
      </div>
    </form>
</template>

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

    export default {
        setup(props, context) {
            const todo = ref('');
            const hasError = ref(false);
            const onSubmit = () => {
                if (todo.value === '') {
                    hasError.value = true;
                } else {
                    context.emit('add-todo', {
                        id: Date.now(),
                        subject: todo.value,
                        completed: false,
                    });
                    hasError.value = false;
                    todo.value = ''; //empty로 바꿔줌
                }
            };

            return {
                todo,
                hasError,
                onSubmit,
            };
        }
    }

</script>

<style>



</style>

 

20. props 사용시 주의할 점

 

Props | Vue.js

 

vuejs.org

  • All props form a one-way-down binding between the child property and the parent one: when the parent property updates, it will flow down to the child, but not the other way around.
  • 자식 컴포넌트에서 부모 컴포넌트의 값을 변경하면 안된다!

 

  • App.vue 소스코드
<template>
  <div class="container">
    <h2>To-Do List</h2>
    <TodoSimpleForm @add-todo="addTodo" />
 
    <div v-if="!todos.length">
      추가된 Todo가 없습니다
    </div>
    <TodoList :todos="todos" @toggle-todo="toggleTodo" />
  </div>
</template>

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

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

  setup() {
    const todos = ref([]);
    const todoStyle ={
      textDecoration: 'line-through',
      color: 'gray'
    };

    const addTodo = (todo) => {
      console.log(todo);
      //자식이 보낸 데이터 확인
      todos.value.push(todo);
    };

    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);
    }

    return {
      todos,
      addTodo,
      todoStyle,
      toggleTodo,
      deleteTodo,
    };
  }
}
</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"
            :value="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
        }
    },
    setup(props, context) {
        const toggleTodo = (index) => {
            context.emit('toggle-todo', index);  // 'toggle-todo'는 이벤트 이름
        };

        return{
            toggleTodo,
        }
    }

}
</script>

<style>

</style>

 

  • TodoSimpleForm.vue
<template>
    <form @submit.prevent="onSubmit">
      <div class="d-flex">
        <div class="flex-grow-1 mr-2">
          <input
            class="form-control"
            type="text" 
            v-model="todo"
            placeholder="Type new to-do"
          >
        </div>

        <div>
          <button 
            class="btn btn-primary"
            type="submit"
          >
            Add
          </button>
        </div>

      </div>

      <div v-show="hasError" style="color: red">
        This field cannot be empty
      </div>
    </form>
</template>

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

    export default {
        setup(props, context) {
            const todo = ref('');
            const hasError = ref(false);
            const onSubmit = () => {
                if (todo.value === '') {
                    hasError.value = true;
                } else {
                    context.emit('add-todo', {
                        id: Date.now(),
                        subject: todo.value,
                        completed: false,
                    });
                    hasError.value = false;
                    todo.value = ''; //empty로 바꿔줌
                }
            };

            return {
                todo,
                hasError,
                onSubmit,
            };
        }
    }

</script>

<style>



</style>

 

21. To-Do 삭제하기

  • App.vue 소스코드
<template>
  <div class="container">
    <h2>To-Do List</h2>
    <TodoSimpleForm @add-todo="addTodo" />
 
    <div v-if="!todos.length">
      추가된 Todo가 없습니다
    </div>
    <TodoList 
      :todos="todos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />
  </div>
</template>

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

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

  setup() {
    const todos = ref([]);
    const todoStyle ={
      textDecoration: 'line-through',
      color: 'gray'
    };

    const addTodo = (todo) => {
      console.log(todo);
      //자식이 보낸 데이터 확인
      todos.value.push(todo);
    };

    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);
    };

    return {
      todos,
      addTodo,
      todoStyle,
      toggleTodo,
      deleteTodo,
    };
  }
}
</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"
            :value="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
        }
    },
    setup(props, context) {
        const toggleTodo = (index) => {
            context.emit('toggle-todo', index);  // 'toggle-todo'는 이벤트 이름
        };

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

        return{
            toggleTodo,
            deleteTodo,
        }
    }

}
</script>

<style>

</style>

 

  • TodoSimpleForm.vue
<template>
    <form @submit.prevent="onSubmit">
      <div class="d-flex">
        <div class="flex-grow-1 mr-2">
          <input
            class="form-control"
            type="text" 
            v-model="todo"
            placeholder="Type new to-do"
          >
        </div>

        <div>
          <button 
            class="btn btn-primary"
            type="submit"
          >
            Add
          </button>
        </div>

      </div>

      <div v-show="hasError" style="color: red">
        This field cannot be empty
      </div>
    </form>
</template>

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

    export default {
        setup(props, context) {
            const todo = ref('');
            const hasError = ref(false);
            const onSubmit = () => {
                if (todo.value === '') {
                    hasError.value = true;
                } else {
                    context.emit('add-todo', {
                        id: Date.now(),
                        subject: todo.value,
                        completed: false,
                    });
                    hasError.value = false;
                    todo.value = ''; //empty로 바꿔줌
                }
            };

            return {
                todo,
                hasError,
                onSubmit,
            };
        }
    }

</script>

<style>



</style>

 

22. emits

Vue3부터 사용되는 emits 형식

 

  • emits를 쓰게 되면 어떤 이벤트들이 진행되는지 한눈에 볼 수 있다.
  • context는 객체를 보내줌.
  • 객체 구조 분해를 통해서 emits 빼오기
  • App.vue 소스 코드
<template>
  <div class="container">
    <h2>To-Do List</h2>
    <TodoSimpleForm @add-todo="addTodo" />
 
    <div v-if="!todos.length">
      추가된 Todo가 없습니다
    </div>
    <TodoList 
      :todos="todos" 
      @toggle-todo="toggleTodo" 
      @deleteTodo="deleteTodo"
      />
  </div>
</template>

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

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

  setup() {
    const todos = ref([]);
    const todoStyle ={
      textDecoration: 'line-through',
      color: 'gray'
    };

    const addTodo = (todo) => {
      console.log(todo);
      //자식이 보낸 데이터 확인
      todos.value.push(todo);
    };

    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);
    };

    return {
      todos,
      addTodo,
      todoStyle,
      toggleTodo,
      deleteTodo,
    };
  }
}
</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"
            :value="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>

 

  • TodoSimpleForm.vue
<template>
    <form @submit.prevent="onSubmit">
      <div class="d-flex">
        <div class="flex-grow-1 mr-2">
          <input
            class="form-control"
            type="text" 
            v-model="todo"
            placeholder="Type new to-do"
          >
        </div>

        <div>
          <button 
            class="btn btn-primary"
            type="submit"
          >
            Add
          </button>
        </div>

      </div>

      <div v-show="hasError" style="color: red">
        This field cannot be empty
      </div>
    </form>
</template>

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

    export default {
        emptis: ['add-todo'],

        setup(props, { emit }) {
            const todo = ref('');
            const hasError = ref(false);
            const onSubmit = () => {
                if (todo.value === '') {
                    hasError.value = true;
                } else {
                    emit('add-todo', {
                        id: Date.now(),
                        subject: todo.value,
                        completed: false,
                    });
                    hasError.value = false;
                    todo.value = ''; //empty로 바꿔줌
                }
            };

            return {
                todo,
                hasError,
                onSubmit,
            };
        }
    }

</script>

<style>



</style>

 

섹션 3. To-Do 검색 기능

23. computed

  • state가 다른 state에 의존할 때 사용하는 state를 말한다.
  • App.vue 소스코드
<template>
  <div class="container">
    <h4>count: {{ count }}</h4>
    <!-- method는 함수 이기 때문에 ()를 쳐주어야 하고, computed는 값이 리턴 되기 때문에 안해주어도 됨. -->
    <h4>double count computed: {{ doubleCountComputed }}</h4>
    <h4>double count method: {{ doubleCountMethod() }}</h4>
    <button @click="count++">Add one</button>
    <h2>To-Do List</h2>
    <TodoSimpleForm @add-todo="addTodo" />
 
    <div v-if="!todos.length">
      추가된 Todo가 없습니다
    </div>
    <TodoList 
      :todos="todos" 
      @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';

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

  setup() {
    const todos = ref([]);
    const todoStyle ={
      textDecoration: 'line-through',
      color: 'gray'
    };

    const addTodo = (todo) => {
      console.log(todo);
      //자식이 보낸 데이터 확인
      todos.value.push(todo);
    };

    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 count = ref(1); 

    // Computed는 인자로 값을 받아올 수 없다.
    // Computed는 함수 안에 reactive state가 존재하고 변경될 떄만 실행되서 그 값을 변수에 저장하게 된다.
    // Computed는 값을 캐쉬한다.? 캐취? 
    const doubleCountComputed = computed(() => {
      console.log('computed')
      return count.value * 2; //항상 2가 곱한 값이 들어가게 됨
    });

    //Compute와 다른 점 : Method는 인자로 값을 받아와서 함수 안에서 사용할 수 있다.
    const doubleCountMethod = (name) => {
      console.log('method')
      return count.value * name;
    };

    // const doubleCountMethod = () => {
    //   return count.value * 2;
    // };

    return {
      todos,
      addTodo,
      todoStyle,
      toggleTodo,
      deleteTodo,
      count,
      doubleCountComputed,
      doubleCountMethod,
    };
  }
}
</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"
            :value="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>

 

  • TodoSimpleForm.vue
<template>
    <form @submit.prevent="onSubmit">
      <div class="d-flex">
        <div class="flex-grow-1 mr-2">
          <input
            class="form-control"
            type="text" 
            v-model="todo"
            placeholder="Type new to-do"
          >
        </div>

        <div>
          <button 
            class="btn btn-primary"
            type="submit"
          >
            Add
          </button>
        </div>

      </div>

      <div v-show="hasError" style="color: red">
        This field cannot be empty
      </div>
    </form>
</template>

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

    export default {
        emptis: ['add-todo'],

        setup(props, { emit }) {
            const todo = ref('');
            const hasError = ref(false);
            const onSubmit = () => {
                if (todo.value === '') {
                    hasError.value = true;
                } else {
                    emit('add-todo', {
                        id: Date.now(),
                        subject: todo.value,
                        completed: false,
                    });
                    hasError.value = false;
                    todo.value = ''; //empty로 바꿔줌
                }
            };

            return {
                todo,
                hasError,
                onSubmit,
            };
        }
    }

</script>

<style>



</style>

 

24. 검색 기능 추가

  • 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 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';

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

  setup() {
    const todos = ref([]);
    const todoStyle ={
      textDecoration: 'line-through',
      color: 'gray'
    };

    const addTodo = (todo) => {
      console.log(todo);
      //자식이 보낸 데이터 확인
      todos.value.push(todo);
    };

    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,
      todoStyle,
      toggleTodo,
      deleteTodo,
      searchText,
      filterdTodos,
    };
  }
}
</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"
            :value="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>

 

  • TodoSimpleForm.vue
<template>
    <form @submit.prevent="onSubmit">
      <div class="d-flex">
        <div class="flex-grow-1 mr-2">
          <input
            class="form-control"
            type="text" 
            v-model="todo"
            placeholder="Type new to-do"
          >
        </div>

        <div>
          <button 
            class="btn btn-primary"
            type="submit"
          >
            Add
          </button>
        </div>

      </div>

      <div v-show="hasError" style="color: red">
        This field cannot be empty
      </div>
    </form>
</template>

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

    export default {
        emptis: ['add-todo'],

        setup(props, { emit }) {
            const todo = ref('');
            const hasError = ref(false);
            const onSubmit = () => {
                if (todo.value === '') {
                    hasError.value = true;
                } else {
                    emit('add-todo', {
                        id: Date.now(),
                        subject: todo.value,
                        completed: false,
                    });
                    hasError.value = false;
                    todo.value = ''; //empty로 바꿔줌
                }
            };

            return {
                todo,
                hasError,
                onSubmit,
            };
        }
    }

</script>

<style>



</style>

실행 화면

 

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

[Vue] 3차시 강의 정리  (0) 2022.07.05