const App = {
data() {
return {};
},
};
<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 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
양끝 공백 제거
전역 컴포넌트를 만드는 경우가 더 적다.
<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와 key는 세트이다. 현재 키 값은 지정해주지 않는데 키는 고유값이여한다. 마치 주민등록번호 처럼,
키 값에 index를 넣는건 최후의 방법이다. 기본적으로 사용하지 마라!. 정말 도저히 키 값을 채울 만한 마땅한 것이 없을 때, 순서를 바꿀 일 없을때는 사용한다.
여기서는 key에 고유한 id를 주기로 한다.
<ul>
<li v-for="todo in todos" :key="todo.id">{{ todo.title }}
</li>
</ul>
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,
};
},
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
투두 데이터만 받아서는 텍스트를 바꿀수가 없다. 그 텍스트가 input요소로 바껴야하는 작업까지 필요함
<button @click="oneditMode">수정!</button>
수정! 버튼을 클릭하면 oneditMode를 실행하라
메소드 만들기
onEditMode() {
this.editMode = true;
},
editMode모드가 true일때 실행.
컴포넌트 만들기?
현재 수정, 삭제 버튼은 li 태그 부분, 즉 투두가 만들어지는 개별적인 항목이다. 이부분을
캡슐화하는 것이 중요하다. 수정 할려면 전체 화면을 제어할 필요 없이 해당 하는 항목 부분만 제어하면 되는 것. 이럴때 컴포넌트가 필요하다.
컴포넌트 생성을 위해 맨 밑에 있는 코드에
app.component('todo-item', TodoItem);
추가
두번째 인수로 들어가는 자리에 변수 명을 입력하고, 따로 쓰면 훨씬 갈끔해짐. 컴포넌트 변수명은 대문자로 시작한다
// 대문자 시작 컴포넌트
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부분을 안에 넣어준다.
<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인 셈이다.
<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
도 위와 같이 수정해준다.
{{ 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,
};
},
todos 배열은 현재 부모 컴포넌트에 있다. 자식은 수정 권한이 없다. todo라는 데이터의 주인은 부모 컴포넌트이다. 즉 타이틀을 수정할려면 자식이 부모에게 요청을 해서 부모가 바꿔줘야 한다.
자식 -> 부모: emits(하위 컴포넌트 이벤트 수신)
부모 -> 자식 : Props
일단 해당 부분은 아래코드로 변경한다.
// 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. 재사용 잊지 말자!!!! 그리고 나는 데이터만 제어한다. 화면 출력은 뷰가 알아서하게끔 한다...중요중요