[TIL # 28] Vue 3일차

Yejin Yang·2022년 5월 19일
0

[TIL]

목록 보기
28/67
post-thumbnail

폼 입력 바인딩

반응형 데이터

const App = {
  data() {
    return {};
  },
};
  • 반응형 데이터를 만들때는? -> data 함수로 만든다.
  • data 함수는 객체리터럴을 반환하는 형식으로 만든다.
    - 컴포넌트 내부에서 데이터함수를 호출하는데 호출하면서 객체리터럴이 반환된다.
    - 함수가 실행될때마다 새로운 객체 리터럴이 반환된다.
  • 함수로 만들어야지 꼬이지 않는다. A에서 수정한 것이 B에서 같이 수정되지 않는다.

데이터 연결

<div id="app">
  
  <input :value="title" />
  <h1>{{ title }}</h1>
  
</div>
// main.js
const App = {
 data() {
  return {
  	title: 'Hello~'
   }
 }
}

현재, HTML의 title는 v-bind로 연결 한 것이다(약어 :)
이것은 단방향 데이터로 연결한 것인데, 화면에서 수정해도 데이터가 바뀌지 않는다. methods도 등록해줘야한다.

methods: {
  changeInput(event) {
    this.title = event.target.value;
  }
}

HTML 수정

<!-- 뷰 문법 내부에서 제공'하는 이벤트 객체 $event
이벤트를 쉽게 참조하는 방법인 $ -->
<input :value="title" @input="changeInput" />

하지만 반응형 데이터란? 데이터가 바뀌면 화면도 같이 바꿔줘야 한다.
자동으로 양방향으로 바꿔주는 디렉티브가 존재한다.
그것은 v-model 이다.

<input v-model="title"/>

위 코드 처럼 작성만 하면 메소드도 필요 없어지고 간편해진다. 꼭 수동으로 해야하는 경우 아니면 자동으로 양방향으로 되는 걸 사용해야된다.


Input의 여러 타입

사용자에게 데이터를 입력 받는 요소인 input
그런 input은 여러 타입이 존재한다. 파일, 라디오, 체크박스 등등..

<input type="checkbox" v-model="checked">

<input type="radio" name="yejin" value="H1" v-model="radio" />
<input type="radio" name="yejin" value="H2" v-model="radio" />
<h2>{{ radio }}</h2>

여기서 name속성은 지워줘도 된다 왜냐하면 같은 모델로 연결되고 있기 때문이다.

const App = {
  data() {
    return {
      title: 'Hello~',
      checked: true,
      radio: '',
    };
  },
};

Vue.createApp(App).mount('#app');

그런데 한국어, 중국어, 일본어를 입력 받을 때 v-model은 이슈가 있다.

IME (중국어, 일본어, 한국어 등)가 필요한 언어의 경우 IME 중 v-model이 업데이트 되지 않습니다. 이러한 업데이트를 처리하려면 input 이벤트를 대신 사용하십시오.

<input :value="title" @input="title = $event.target.value" />
<input v-model="title" />

두 줄의 코드 중 v-bind방식을 이용하면 된다.


수식어

.lazy

<!-- 수동 -->
<input :value="title" @change="title = $event.target.value" />

<!-- 자동 -->
<input v-model.lazy="title" />
      <h1>{{ title }}</h1>

.lazy 수식어를 사용한다면 실시간으로 바뀌지 않고 엔터를쳤거나, 값이 바뀌거나, input창에 블러(포커스 해제)되었거나 할 때 값이 바뀐다.
수동으로 만들면 @change이벤트랑 같다.(v-on)

.number

<input v-model.number="title" />
      <h1>{{ title }}</h1>

Number() 나 parseInt 같은 것이다.
템플릿상에서 형 변환 해주는거라서 편리하다.

.trim
양끝 공백 제거


컴포넌트

개념

  1. 캡슐화(모듈화)
  2. 재사용
    캡슐화를 안하더라도 재사용용도를 쓰거나, 재사용을 안하더라도 캡슐화(묶어내는 용도)로 쓰임

종류

  1. 전역 컴포넌트
  2. 지역 컴포넌트

전역 컴포넌트를 만드는 경우가 더 적다.

TODO List 실습

<input v-model="title" @keydown.enter="addTodo" />

-> 키보드의 enter키를 입력하면 addTodo 메소드가 실행된다고 먼저 html에 정의

const App = {
  data() {
    return {
      title: '',
      todos: [],
    };
  },
  methods: {
    addTodo() {
      // 타이틀이라는 데이터를 투두라는 빈배열에 밀어넣기 (데이터상)
      this.todos.push(this.title);
      // 타이틀 초기화
      this.title = '';
    },
  },
};

-> 배열 데이터를 화면에 출력할때 v-for를 쓸 수 있다.

<ul>
   <li v-for="todo in todos">
    {{ todo }}
   </li>
</ul>
  • v-for : todos라는 배열에 in을 사용하여 todo 라는 개별적인 아이템을 지정
  • 이중 중괄호로 화면에 출력

그런데, v-for와 key는 세트이다. 현재 키 값은 지정해주지 않는데 키는 고유값이여한다. 마치 주민등록번호 처럼,
키 값에 index를 넣는건 최후의 방법이다. 기본적으로 사용하지 마라!. 정말 도저히 키 값을 채울 만한 마땅한 것이 없을 때, 순서를 바꿀 일 없을때는 사용한다.

여기서는 key에 고유한 id를 주기로 한다.

<ul>
  <li v-for="todo in todos" :key="todo.id">{{ todo.title }}
  </li>
</ul>

고유한 id 주는 방법 nanoid CDN

import { nanoid } from 'https://cdn.jsdelivr.net/npm/nanoid/nanoid.js'

const App = {
 data() {
    return {
      title: '',
      todos: [
        { id: nanoid(), title: 'ABC' },
        { id: nanoid(), title: 'XYZ' },
      ],
      editMode: false,
    };
  },
  • nanoid CDN을 import 키워드로 연결해준다.
  • id: nanoid()

내용이 없을 때, 공백 문자 등록(엔터)되지 않게 하기

methods: {
    addTodo() {
      if (!this.title.trim) {
        // 타이틀이 없으면 멈춰라
        return;
      }
      this.todos.push(this.title);
      this.title = '';
    },
  },
};

if(this.title.trim === '') 이렇게 사용할 수도 있겠지만, 간단히 if (!this.title.trim) 이렇게 사용할 수 있다.
공백은 falsy 인데, 부정연산자 !를 사용해서
빈문자(falsy)가 부정되어 true로 된다.

.trim 메소드를 사용해서 공백 입력을 막아준다.


삭제 버튼 만들기

<!-- deleteTodo를 호출할건데 삭제 버튼 눌렀을때 누른 todo가 객체로 들어온다. -->

<button @click="deleteTodo">삭제!</button>

삭제 버튼을 만들고, 사용자가 많은 항목 중 어떤 것을 지웠는지 알아내야한다.

참고로 데이터를 바꾸면 뷰가 알아서 지워주는 것을 화면까지 제어하지말자. 나는 데이터만 제어해주면 된다. 데이터에 맞게 화면이 출력되면 된다.(중요)

메소드 등록

// return 키워드 사용 
deleteTodo(todoToDelete) {
   this.todos.findIndex(todo => {
     return todo.id === todoToDelete.id
    })
   this.todos.splice(index, 1)
    }

// 간편화 + index 변수에 담아서 사용
deleteTodo(todoToDelete) {
     const index = this.todos.findIndex(todo => todo.id === todoToDelete.id)
     this.todos.splice(index, 1)
    }

this.todos.splice(index, 1)
배열에서 몇번째(인덱스 번호)인지 알아내서 하나 지우겠다.

this.todos.findIndex
몇 번째 인지 알아내는 방법 .findIndex


수정 버튼 만들기

  1. 수정할 수 있는 input창이 나와야함
  2. 수정 인풋창 내용에 기존에 있던 텍스트가 보여야함

투두 데이터만 받아서는 텍스트를 바꿀수가 없다. 그 텍스트가 input요소로 바껴야하는 작업까지 필요함

<button @click="oneditMode">수정!</button>

수정! 버튼을 클릭하면 oneditMode를 실행하라

메소드 만들기

    onEditMode() {
      this.editMode = true;
    },

editMode모드가 true일때 실행.

컴포넌트 만들기?
현재 수정, 삭제 버튼은 li 태그 부분, 즉 투두가 만들어지는 개별적인 항목이다. 이부분을
캡슐화하는 것이 중요하다. 수정 할려면 전체 화면을 제어할 필요 없이 해당 하는 항목 부분만 제어하면 되는 것. 이럴때 컴포넌트가 필요하다.

컴포넌트 생성을 위해 맨 밑에 있는 코드에
app.component('todo-item', TodoItem);
추가
두번째 인수로 들어가는 자리에 변수 명을 입력하고, 따로 쓰면 훨씬 갈끔해짐. 컴포넌트 변수명은 대문자로 시작한다


TodoItem 컴포넌트(li태그 캡슐화)

// 대문자 시작 컴포넌트
const TodoItem = {
  template: /* HTML */ `
  <li>
  {{ todo.title }}
  <button @click="onEditMode">수정!</button>
  <button @click="deleteTodo">삭제!</button>
  </li>
  `,
  data() {
    return {
      // 유효범위는 이 컴포넌트 안임
      // App에 있는 타이틀은 그 컴포넌트에서만 사용하는것임
      // 이름 중복 상관 없음
      title: '컴포넌트'

    };
  },
  methods: {
    onEditMode() {

    },
    deleteTodo() {

    }
  }
};

const app = Vue.createApp(App);
// 컴포넌트는'todo-item' 이 이름으로 사용하면된다. 이름으로 html에 태그로 만든다.
app.component('todo-item', TodoItem);
app.mount('#app');

template 태그에 백틱기호를 사용해서 li부분을 안에 넣어준다.

props 개념

<ul>
  <todo-item v-for="todo in todos" :key="todo.id" :yejin="todo"> </todo-item> </ul>

태그로 원래 li태그가 있던 부분에 바꿔준다.
:yejin="todo"라고 적은 부분은 반복되는 todo라는 데이터를 통로로 밀어 넣을 수 있는데 통로 이름이 yejin인 것 이다.

todo는 객체데이터다. yejin은 반응형데이터라서 todo를 받아오고 곧 yejin=todo인 셈이다.

  1. todos가 가지고 있는 각각의 아이템들을 todo-item이라는 컴포넌트에 yejin이라는 통로로 넣어준다.
  2. props 옵션으로 받아서 내부에서 반응형 데이터로 활용그런데 yejin은 예시일뿐 적합하지 않으니 이름 변경
<ul>
  <todo-item v-for="todo in todos" :key="todo.id" :todo="todo"> </todo-item> 
</ul>
  • :to : 속성
  • "todo" : 데이터
const TodoItem = {
  template: /* HTML */ `
    <li>
      <span v-if="!editMode">{{ todo.title }}</span>
      <input v-else v-model="todo.title" />
      <button @click="onEditMode">수정!</button>
      <button @click="deleteTodo">삭제!</button>
    </li>
  `,
  props: {
    todo: Object,
  },

컴포넌트에 props를 추가해주는데 객체 데이터라 Object로 객체데이터라는 타입을 명시해준다.

template도 위와 같이 수정해준다.

  • editMode가 아닌 경우에는 타이틀을 그냥 출력({{ todo.title }})한다.

그런데, 중간에 아래 코드 처럼 쓰면 에러가 난다.
현재는 CDN으로 가져온 경우라 에러가 안나는 것 뿐.

<input v-else v-model="todo.title" />

v-model은 양방향 데이터다. 컴포넌트 사이에서 연결하는것은 양방향 데이터가 안된다.

todo-item 컴포넌트는 App의 자식 컴포넌트이다.

const App = {
 data() {
   return {
     title: '',
     todos: [
       { id: nanoid(), title: 'apc' },
       { id: nanoid(), title: 'xyz' },
     ],
     editMode: false,
   };
 },

emits 개념

todos 배열은 현재 부모 컴포넌트에 있다. 자식은 수정 권한이 없다. todo라는 데이터의 주인은 부모 컴포넌트이다. 즉 타이틀을 수정할려면 자식이 부모에게 요청을 해서 부모가 바꿔줘야 한다.

자식 -> 부모: emits(하위 컴포넌트 이벤트 수신)
부모 -> 자식 : Props

일단 해당 부분은 아래코드로 변경한다.

  • v-model을 사용하지않고 단방향바인딩으로 변경해놓는다.
    // TodoItem 백틱 안 내용임
     <input v-else :value="todo.title" @input="inputTitle" />
  inputTitle(event){
      // update-title 하면 타이틀 수정해달라고 요청 할 수 있음
      this.$emit('update-title', event.target.value)
    },

$emit 사용해서 자식이 update-title라고 부모에게 요청 -> 커스텀 이벤트

근데 어떤 걸 수정해줄지 정해줘야함 event.target.value이걸로 수정할 값을 준다.

 <ul>
    <todo-item v-for="todo in todos" :key="todo.id" :todo="todo"
       @hello="todo.title = $event">
     </todo-item>
 </ul>

@hello="todo.title = $event"
이벤트 발생은 v-on (약어: @)
$event객체에 event.target.value 값이 들어온다. 자식 컴포넌트가 넣어준 값이며 부모는 이 값으로 타이틀을 갱신한다.

확인 버튼 만들기

<button v-else @click="offEditMode">확인</button>

  
  // 부모 컴포넌트
  const App = {
  data() {
    return {
      title: '',
      todos: [
        { id: nanoid(), title: 'ABC' },
        { id: nanoid(), title: 'XYZ' },
      ],
  
  // 수정 권한은 부모에게 있다.(기본 값 false로 지정)
      editMode: false,
    };
  },

// -- 중략 --
  
 // 자식 컴포넌트 
const TodoItem = {
  template: /* HTML */ `
    <li>
 <!-- true가 되어 수정 input창에 내가 적었던 투두 타이틀들이 보인다. -->     
      <span v-if="!editMode"> {{ todo.title }} </span>
     
      <input v-else :value="todo.title" @input="inputTitle" />
     
 <!-- 수정 버튼을 클릭하면 기본값인 editMode가 부정되어 true가 된다.(수정 가능) -->
      <button v-if="!editMode" @click="onEditMode">수정!</button>
      
 <!-- 확인 버튼을 다시 true에서 기본값인 false로 된다.(수정 닫힘) -->
      <button v-else @click="offEditMode">확인!</button>
      <button @click="deleteTodo">삭제!</button>
    </li>
  `,
  props: {
    todo: Object,
  },
  data() {
    return {
      title: '컴포넌트',
      editMode: false,
    };
  },
  methods: {
    onEditMode() {
      this.editMode = true;
    },
    offEditMode() {
      this.editMode = false;
    },
    inputTitle(event) {
      this.$emit('update-title', event.target.value);
    },
    deleteTodo() {},
  },
};

회고

간단한 투두 만들기 실습으로 컴포넌트가 필요한 이유와 컴포넌트 구조에 대해서 배울 수 있었다. 실시간으로 강의들으면서 나만 볼 수 있도록 필기해둔거라 군데 군데 부족한 부분이 많다.(아주 만약 읽으시는 분이 있다면 참고 부탁드립니다..😅) 컴포넌트의 필요성 1. 캡슐화 2. 재사용 잊지 말자!!!! 그리고 나는 데이터만 제어한다. 화면 출력은 뷰가 알아서하게끔 한다...중요중요

profile
Frontend developer

0개의 댓글