내가 그동안 해온 멍청한 코드를 반성하며 작성해본다.
이 글을 작성하는 이유는 v-model이라는 요소를 어떻게하면 제대로 이해하고 쓸 수 있을까를 넘어 emit과 props에 대한 완벽한 이해 그리고 사용에 대한 어려움이 없게 하기 위해서 써보는 글이다.
1. 개념적인 v-model이란?
개념적인 v-model이란 무엇일까? 정의해보자
v-model을 컴포넌트에서 사용하여 양방향 바인딩을 구현할 수 있습니다.
라고 공식 문서에 기재되어있다. 즉 컴포넌트간에 소통을해주는데 쓰인다는 건데 그럼 양방향에 대한 내용을 공식 문서로 풀어보면
<input v-model="searchText" />
위 코드를 확인해 보면 나온다. v-model을 이용해서 searchText라는 애를 할당시켜주었다.
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
위에 v-model을 풀어보면 위와 같이 풀이되는데 이걸 제대로 파악하지 못하면 나처럼 돌고 도는 멍청한 짓을하게 된다. 다른 블로그와는 다를 것이다. 난 비전공자로 처음부터 풀어보려고 하기 때문..에
<input
:value="searchText"
위의 코드는 무엇인지를 알아야 뭘할 수 있다. 이건 바인딩이다.
v-bind:value를 함축적으로 쓴것이다. 즉 바인딩과
@input="searchText = $event.target.value"
이건 v-on의 단축 문법인 @을 사용하여 input에 붙여서 사용한다. 즉
@input에 인풋 이벤트를 활용하고 있는 것이다. 중요하다. 아래 참고해라
/>
input에 이벤트는 여러 종류가 있다.
https://ux.stories.pe.kr/119 블로그를 참고해보면 많은 이벤트를 활용할 수 있다. 이때 내가 사용해본 @input에 대한 설명을 잠깐하면 input 엘리먼트에 변화가 생겼을때를 지칭하게 된다. 엘리먼트는 html의 요소를 엘리먼트라고 칭한다.
그럼 이제 위의 v-model을 컴포넌트에 사용할 때에 대한 예시를 확인해보자
<CustomInput
:modelValue="searchText"
바인딩한다 뭐를? 이거 중요하다. 위에 우리가 작성한 코드를 뜯자
:value="searchText"를 바인딩한다.
@update:modelValue="newValue => searchText = newValue"
그리고 바인딩 된 modelValue가 update가 될 경우 이벤트를 실행하게 된다.
/>
modelValue
에 <input>
엘리먼트에 value
속성을 바인딩한다.input
엘리먼트가 만약 트리거 즉 변화되면 사용자가 설정해 놓은 이벤트를 내보낸다.2. 이름있는 v-model?
내가 이번에 부여 받은 과제이다. 감사한 선임 분들이 주신 과제이고 내가 진지하고 고민해보고 싶었던 부분이었다.
기본적으로 컴포넌트의 v-model은 modeValue를 프로퍼티로, update:modelValue를 이벤트로 사용한다.
v-model에 인자를 전달하여 이러한 이름을 수정할 수 있다고한다.
이렇게 쉽게 써놔도 나란 멍청이는 뭔 개소리야 시... 이라고생각한다.
그럼 하나하나 차근차근 보자
<MyComponent v-model:title="bookTitle" />
나와있는 예시를 확인해보자.
v-model:title="bookTitle"
이렇게 되어있다. 아까와는 다르게 v-model옆에 title이라는 명이 붙어있다.
이러한 경우에는 자식 컴포넌트는 title이라는 프로퍼티를 예상하고 그 후 부모 값을 업데이트하는 update:title이벤트를 발생 시켜 줘야한다고한다. 그래?
즉 자식 컴포넌트에서 데이터를 올려주면 되겠네? 라고 생각이 도달한다.
<!-- MyComponent.vue -->
<script setup>
아래 프롭스로 데이터를 받아오는데 이건 title라고 지정되어있다.
그래 위에 v-model:title이라고 지정해놓은 곳과 연관 되어있네?
defineProps(['title'])
에밋은 데이터를 업데이트 ? 즉 부모에게 전달할껀데 뭐 어쩔때 전달할건데? 라는 뜻이다. 즉 사용자 지정 이벤트가 변경되면 emit을 실행시켜라!
defineEmits(['update:title'])
</script>
<template>
인풋 엘리먼트다.
<input
type="text"
벨류는 뭐로 되어있어? 그래... 이거야이거 이게 양방향이다.
즉 벨류에 업데이트 되는거다. title의 값이
:value="title"
에잇을 통해서 input이 변경되면 emit을 호출해 그리고 이벤트를 보내
@input="$emit('update:title', $event.target.value)"
/>
</template>
즉 말야 만약에 이렇게 title이라고 이름을 지정할꺼면 말야 하나만 기억하자.
<!-- 자식 컴포넌트 -->
<template>
<input
class="boreder"
type="text"
placeholder="입력해주세요"
:value="title"
@input="emit('update:title', $event.target.value)"
/>
</template>
<script setup>
const props = defineProps([
'title'
]);
const emit = defineEmits([
'update:title'
]);
</script>
<style>
.boreder {
border: 1px solid rgb(61, 6, 61);
}
</style>
<!-- 부모 컴포넌트 -->
<template>
<h2>v-model / defineModel Input Study</h2>
<div>
<VModelInput
style="margin-top: 20px;"
v-model:title="updateValue"
/>
<h3 style="margin: 10px;">
{{ updateValue }}
</h3>
</div>
</template>
<script setup>
import { ref } from 'vue';
const updateValue = ref('');
const updateDefineModelValue = ref('');
</script>
즉 말야 위의 코드와 같이 실현을 해보면 다른 것 없다...
v-model:title="updateValue"
로 title이라는 v-model의 이름을 정해줫으면 받는 자식한테도 이름을 알려줘야지...
어떻게?
:value="title" @input="emit('update:title', $event.target.value)"
value에 title이라는 props로 받아온 데이터를 넣어주고 그럼?
입력 된건 어떻게? 그래야 소통 양방향이 될거아님?
update:즉 title이 업데이트 되면 event.target에서 value 값을 emit으로 전달할꺼야~ 즉 이렇게 양방향을 만든다.
정리
v-model은 왜 양방향이야?
그렇다.. 왜? v-model을 써야해? 라고 물어보면 뭐라고 답할 것 인가?
간략한 예만 보자면 component에서 작성할 것들이 줄어서 사용하는것인가? 뭐 맞다...고 생각한다 다만 그냥 쓰는게 아니라 효과적으로 읽을 수 있는 것도 사실이다.
v-model이 여러개일때는? v-model:이름 이런식으로 정해준다면 효과적으로 사용이 가능하고 또한 받아주는 곳도 중요한데 컴포넌트에선 쉽게 작성된 v-model을 보고 아~ 자식놈하고 소통하는 부모님이네~ 이지만 자식새끼를 뜯어 보면 분명 부모님께 명령을 받고 명령받은 일에 결과를 말해주는 곳이 있을것이기 때문이다. 그것을 쉽게하는데 defindeModel이고 그게 바로 useModel인 것이다.
defindeModel
자식에게 자유를 주다.
이 기능을 사용하려면 추가적인 설치와 세팅을 해주어야한다.
<template>
<input
placeholder="입력해주세요"
v-model="input"
>
</template>
<script setup>
const input = defineModel('handlerInput');
</script>
위 코드를 확인해보면 알 수 있듯 v-model로 작성된 곳에 input은 아래 secript로 설정해놓은 변수를 지정해놓는데 그것을 또 보면 defineModel이 설정된 것을 확인할 수 있다.
이건 이번 vue3.3에서 추가 된 기능으로 자세한 내용은
https://blog.vuejs.org/posts/vue-3-3
위의 에반유의 블로그를 들어가서 확인할 수 있다.
간단하게 훝어보면
<template>
<input :value="modelValue" @input="onInput" />
</template>
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
console.log(props.modelValue)
function onInput(e) {
emit('update:modelValue', e.target.value)
}
</script>
위의 코드 처럼 원래는 이렇게 써야하는데 말야...
<template>
<input v-model="modelValue" />
</template>
<script setup>
const modelValue = defineModel()
console.log(modelValue.value)
</script>
이렇게 줄일 수 있어! 이다. 즉 자식 요소에서도 emit과 props에서 자유를 줄 수 있다.
<!-- 자식 컴포넌트 -->
<template>
<input
placeholder="입력해주세요"
v-model="input"
>
</template>
<script setup>
const input = defineModel('handlerInput');
</script>
<!-- 부모 컴포넌트 -->
<template>
<div>
<DefineModelInput
v-model:handlerInput="updateDefineModelValue"
/>
<h3 style="margin-top: 10px;">
{{ updateDefineModelValue }}
</h3>
</div>
</template>
<script setup>
import { ref } from 'vue';
const updateDefineModelValue = ref('');
</script>
위의 과정을 생략해서 이렇게 간단하게 defindeModel을 사용할 수 있게한다!...
하지만 나는 이 과정을 다시 역순으로 못풀어냈다.. 즉 제대로 이해 못했던것이다. 이 글을 작성하는 이유기도하다...
emit, props에 대한 고찰
emit은 뭐고 props는 뭔데는 설명해도 그럼 써봐는 안되는 이들에게...
그냥 이해 못해서 그런거다 이해하자...
난 비전공자라 그런지 확실히 컴퓨터처럼 생각하는것을 잘 못한다. 전공자가 들으면 읭? 뭔개솔 할 수 있는데... 이게 참 1에서 2 다음 3 다음 4가 되야 1이 실행 되를 유추하는게 참 어렵다... 그래서 슬슬 생각을 변경하고 제대로 이해해보려고 노력하려고한다.
예를 들면 위에 코드 중 자식 컴포넌트에선 emit과 props 데이터를 처리하기 위해서 부모에게 props 데이터를 받아오고 emit으로 변경된 데이터를 보내주고하는 데이터의 처리 흐름을 이해하기 쉽지 않다는 것이다. 그걸 이제 부터 고민하고 제대로 이해하고 노력해보려고한다.
자 이제 분석에서 어려웠던 부분을 풀이해보면
문제점 1. emit() 내부 인자
컴포넌트에 이벤트에 대한 부분이다.
https://ko.vuejs.org/guide/components/events.html
공식 문서를 기초로 내 방식대로 풀이해보았다.
<button @click="$emit('someEvent')">클릭하기</button>
내장 메서드인 emit을 사용하여 v-on에서 직접 사용자 정의 이벤트를 발신할 수 있다고한다. 즉 내장으로 내제 된 emit이란놈을 쓰면 @ v-on에서 발신 전화 걸수 있다 즉 이벤트를 이때 보낼 수 있다는 것이다.
<MyComponent @some-event="callback" />
이제 전화를 걸었으니깐 받아야지 수신 받는 곳은 부모이다. 부모에서는 내가 보낸 이벤트 명으로 전화 번호를 확인하고 받을 수 있다.
some-event는 카멜 케이스를 케밥 케이스로 변환한것이다...
카멜 -> someEvent
스네이크 -> some_event
케밥 -> some-event
이벤트 인자
이벤트와 함께 특정 값을 내보내는 것이 때때로 유용하다고한다.
추가 인자를 포함해서 전달할 수 있다고한다.
$emit()에서 이벤트 이름 뒤에 전달된 모든 추가 인자는 리스너로 전달됩니다. 예를 들어,
리스너? 리스너에는 특정 이벤트가 발생했을 때 실행되는(이벤트를 처리할(메서드) 라는데? 더 알아보자 솔직히 이해 못함
$emit('foo', 1, 2, 3)을 사용하면 리스너 함수는 세 개의 인자를 받습니다.
이벤트 리스너
-> 이벤트 리스너란 이벤트가 발생했을 때 그 처리를 담당하는 함수를 가리키며, 이벤트 핸들러(event handler)라고도 합니다.
다른 블로그를 찾아보니 두 번째 파라미터부터는 상위 컴포넌트에 실행하는 함수에 전달할 파라미터라고한다.
(property) emit: (event: "update:title", ...args: any[]) => void
...args
는무엇인지 찾아보자.
찾아 본 결과 블로그와 조합했을때 결론에 도달한 것은 파라미터 즉 매개변수(parameter)인듯하다. 전달인자란 뜻을 가진 argument를 함축한 args 를 봐도 emit에 두 번째부터는 아귀먼트 즉 전달인자를 전달한다 함수의 전달인자이다.
문제점 2. v-on 이벤트가 실행되는 흐름을 파악해라
그냥 v-on을 막사용하는게 아니라 어떻게 움직이는지 파악해보자.
v-on이란?
엘리먼트에 이벤트 리스너를 연결한다.
세부로 공식문서를 읽으며 눈에 딱 들어온 부분은
일반 엘리먼트에 사용되면 네이티브 DOM 이벤트만 수신합니다. 커스텀 엘리먼트 컴포넌트에서 사용되는 경우, 해당 자식 컴포넌트에서 발송(emit)하는 커스텀 이벤트를 수신합니다.
이 부분이었다. 즉 일반적인 엘리먼트에서 사용하면 dom
커스텀 엘리먼트에서 사용하면 자식에서 보내온 emit의 이벤트를 수신...
또 네이티브 DOM 이벤트를 수신할 때, 메서드의 인자는 네이티브 이벤트 뿐이다. 인라인 명령문을 사용하는 경우, 명령문은 특수 속성인 $event
로 v-on:click="handle('ok', $event)"
와 같이 이벤트 객체에 접근할 수 있다고한다.
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>
이런식에 객체 문법도 사용한다는데 아직까진 회사 내에 프로젝트에서 보진 못했다.
즉 정리해보면
→ 일반적으로 엘리먼트에서 사용한 경우에는 dom에서 발생한 event를 전달한다. 이건 객체형태로 된 이벤트 객체에 접근할 수 있도록하는 것이고 커스텀 이벤트인 update:modelValue등으로 작성할 경우에는 emit의 이벤트를 수신한다...
@input="emit('update:title', $event.target.value)"
즉 내가 만든 커스템 네임의 경우 이벤트에 접근하기 위해서 $event로 접근한다.
잠깐 정리를 멈추고 브라우저 이벤트란 개념을 잡고 넘어가려한다.
브라우저 이벤트란?
이벤트란? 어떤 사건을 의미한다.
브라우저에서 이벤트란 DOM 요소와 관련이 있다.
이벤트가 발생하면 그에 맞는 반응을 해야한다. 이를 위해서 이벤트는 일반적으로 함수에 연결된다. 그 함수는 이벤트가 발생하기 전에는 실행이 안되다가 이벤트가 발생하면 실행.
이러한 함수를 이벤트 핸들러라고 한다...
출처: 인파님 블로그
그림을 보면서 이해했는데
1. 사용자가 페이지와 상호 작용을 통해 클릭을 눌렀다.
2. 이벤트가 발생.
3. 응답을 통해 자바스크립트의 코드가 실행된다.
4. 결과적으로 페이지의 모양이 어떤 식으로든 업데이트/수정됩니다.
내가 진짜 고쳐야하는 잘못 된 버릇이 확대 해석이다..
디자인과 기획 처럼 뭔가를 확대해서 청사진을 그리지 말고 상황만 해석하자.
문제점 3. v-for 사용 시 key를 써야 되는 이유 확인 필요
key를 사용하는 이유에 대하여 생각해보자.
이전 회사에서는 생각없이 key를 안썻다가 warning 만나는 일이 잦았다.. 그래서 걍 의무적으로 썻었던 기억이 있는데 그래서 이게 왜 필요한지는 고민도 안한 멍청이가 맞다... 그럼 고민을 해보자.
공식 문서 에 따르면
하위 트리에서 내부 컴포넌트 상태를 유지하기 위해 컴포넌트에는 v-for
와 함께 key
가 항상 필요하다고한다.
<template>
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</template>
<script setup>
import { ref } from 'vue'
const parentMessage = ref('Parent')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
</script>
근데 또 공식문서 예제를 확인해보면 key를 안썻다?
그러다 아래를 쭉 내리다보니 key를 통한 상태유지
가 나왔다.
근데 뭔 알아듣기 어렵게 되어있다 걍 내가 빡대가리인듯하다.
그럼 다른 분의 정리를 찾아보자...
우선 key의 역할은 반복되는 내용을 관리하기 위해서 사용하는 유니크 값..이다...
새 노드 목록을 이전 목록과 비교하기 위한 힌트로 제공된다.
그리고 공식문서에도 나온 in-place patch
전략은 vue는 알다싶이 임시DOM을 사용해서 바뀌는 부분만 업데이트하는 그것을 말한다.
https://vueschool.io/articles/vuejs-tutorials/tips-and-gotchas-for-using-key-with-v-for-in-vue-js-3/
여기 블로그를 참조를 많이했다.
v-for는 key없이 작성이 가능하다 하지만 eslint에서 오류를 낸다.
key는 고유해야한다 : id 또는 uid를 사용하여 고유하게 지정할 수 있도록해야한다고 조언한다.
id가 배열에 있는 항목이 아닐 경우는 고유한 값이 보장되는 다른 값이 있다면 해당 값을 사용할 수 있다.
주의할 점은 인덱스는 항목 자체의 식별자가 아니라 배열의 항목 위치만 나타내기 때문에 배열 인덱스를 키로 사용 금지 xxxx
X <div v-for="(user, index) in users" :key="index">
그리고 찾다보니 재밋는 ㅋㅋ 블로그 글도 찾았다...
https://kamang-it.tistory.com/entry/WebPerformanceVue-vfor%EA%B3%BC-key-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%84%B1%EB%8A%A5%EC%82%AC%EC%9D%B4%EC%9D%98-%EA%B4%80%EA%B3%84
이 글에선 key 없을때, 중복, 유니크로 나눠 테스트를 진행하시기도해서 보면 이해가 빠를듯해서 찾아보았다.
이분에 결론은
1. key는 성능에 영향을 확실하게 미친다.
2. key가 없는 경우 데이터가 삽입 정도와 정렬정도에 따라서 캐싱효과를 보고 못보고가 결정된다.
3. key가 중복되는데 캐싱을 못볼경우 그냥 재 렌더링 해버리는데 그 사이드 이펙트가 클 수도 있다.
4. key를 쓸꺼면 아예 중복안되게 key를 만들고 안 쓸거면 그냥 쓰지 않는게 낫다.
결론 중의 결론
그냥 key쓰세요. 아 그리고 무조건 유니크하게
이렇게 결론이 도달하셨다.
찾다가 공식 문서에서 빌트인 특수 속성에 관한 내용을 찾았다.
딱 써있다. 위에 삽질을 하다 조금은 이해가 되고있다... 긍정적...
→ 특수 속성 key
는 Vue의 가상 DOM 알고리즘이 이전 목록과 새 노드 목록을 비교할 때 vnode를 식별하는 힌트로 주로 사용된다.
내가 그래서 새로 한번 이해하려고 테스트해봣다.
한번씩 클릭했을때 확 차이를 느낄 수 있었다. 그래... 키를 넣으면 이렇게 유지를 시키며 그 바뀐 부분 만 변경된 것을 바꿀 수 있구나...
그 증거로는 위의 사진과 아래의 input 필드에 초기화가 되지 않기 때문에
바뀐 부분만 업데이트하고 있다는 것을 확인할 수 있다.
<template>
<div>
<h2>키 테스트 키 없음</h2>
<ul>
<li
v-for="item in items"
>
{{ item }} <input type="number"/> 개
</li>
</ul>
<v-btn @click="shift">버튼</v-btn>
</div>
<!-- 키를 추가해서 이동을 매칭을 시켜서 노드에 매칭해줌 -->
<div style="margin-top: 30px;">
<h2>키 테스트 key 유니크하게 맞춰줌</h2>
<ul>
<li
v-for="item in itemskey"
:key="item"
>
{{ item }} <input type="number"/> 개
</li>
</ul>
<v-btn @click="shiftOn">버튼</v-btn>
</div>
</template>
<script setup>
import { ref } from 'vue';
const items = ref([
'사과', '바나나', '포도',
]);
const itemskey = ref([
'사과', '바나나', '포도',
]);
const shift = () => {
items.value.push(items.value.shift());
}
const shiftOn = () => {
itemskey.value.push(itemskey.value.shift());
}
</script>
테스트해본 코드이다.
문제점 4. 키 입력 이벤트에 대해서 확인 필요
위 테스트 코드에서는 ${} 를 사용하지 않고 그냥 item을 키로 넣었지만 우리 회사 프로젝트에서는 ${} 형식을 사용했다... 그 이유? 생각없이 쓰지말고 알아보자...
일단 회사 코드내에서 key에 사용할때는 :key="빽틱item${index}빽틱"
벨로그에선 빽틱이 안되네 ㅋㅋ;; 형식으로 사용하고있다. 그럼 빽틱은 무엇인가?
const num1 = 10;
const num2 = 20;
console.log(num1 + ' + ' + num2 + ' = ' + (num1+num2) + " 입니다.");
const num1 = 10;
const num2 = 20;
console.log(`${num1} + ${num2} = ${num1+num2} 입니다.`);
위코드 예제 처럼 조금 더 쉽고 효과적으로 사용할 수 있도록 돕는것...
${}
를 통해서 문자열과 변수를 적절하게 같이 사용이 가능하다.
${}
란? : 표현식 삽입(Expression interpolation)
사진과 같이 계산식을 넣을 수 있었다.
확실히 ${} 빽틱과 조합해서 사용하면 훨신 더 효과적으로 사용이 가능했다.
그렇다면 for문안에서 나오면서 아마도
name0, name1, name2
형식으로 나오지 않을까 싶다!
근데 왜? 아니... 이해안가는게 그럼 왜 ... name0, 1, 2이런식으로 만들어주지? 매칭 시키는것도 아닌데?... 의 의문이든다...
선임분들이 이야기해주셨던 것은 우린 키로 뭘 추후에 따로 사용한 적 없어요가... 이런... 뜻?~
→ 우리회사에서 사용한 방식은 일단 일시적으로 박아놓은 상태로 유지하기 위해서 넣은 것이다. 즉 만약 api 정의서에 고유한 값이 있을 경우엔 그 값을 사용해서 key를 돌리면 된다. 즉 id가 있을 경우엔 아이디가 고유하기 때문에 사용할 수 있다.
<div style="margin-top: 30px;">
<h2>키 테스트 key 유니크하게 맞춰줌</h2>
<ul style="margin-top: 20px;">
<li
v-for="item in itemskey"
:key="item.id"
>
{{ item.label }} <input style="margin-left: 60px;" type="number"/> 개
</li>
</ul>
<v-btn @click="shiftOn">버튼</v-btn>
</div>
<script setup>
import { ref } from 'vue';
const itemskey = ref([
{
id: 0,
label: '푸바오',
value: '0'
},
{
id: 1,
label: '꿈돌이',
value: '1'
},
{
id: 2,
label: '호돌이',
value: '2'
},
]);
const shiftOn = () => {
itemskey.value.push(itemskey.value.shift());
}
</script>
위 처럼 바꿔서 고유하게 key를 사용할 수 있다. 이렇게 되면 실무에서 에러를 방지할 수 있을듯하다.
그리고 추가적인 질문을해주셨다.
그러면 component 단위로 v-for를 돌릴때 키의 이름이 같아도 되는건가요~? → 정답은 괜찮다. 왜냐 컴포넌트 단위로 랜더링이 되기 때문이다.
문제점 5. 개발자 콘솔에서 에러났을 때 디버깅하는 방법 확인 필요
난 1년 동안 이지에서 개발할 때 개발자 콘솔도 못 읽으면서 했던거다...
개발자 도구를 사용하는 방법에 대해 고민해본적있는가? 사수가 알려주지 않아도 고민했어야하는 부분이었다...
내가 또 다시 정리한 방법
이 로그를 확인해보면 빠졌다. 마지막 닫는 요소가? 여기서 제일 핵심 내용은 1, 2, 3이 의미하는 역할이다.
즉 오류 유형: 오류의 내용, 회색글씨 오류의 위치
인 것 이다.
JS 디버깅 방법...
출처 : 🕷
개발자 도구에서도 디버깅이 된다. 나는 vscode에서 버그를 확인했었는데...
개발자 도구에선 아~ 오류네 읽자 걍 그러고 붙여넣기로 번역기에 던지곤했다... 반성한다...
글을 정리하며 방대한 글에서 ... 찾는 요소가 있을실 경우... ctrl + f로 원하는 키워드를 찾아서 보시길....