Vue.js의 가장 강력한 기능인 컴포넌트를 활용하기 - 초급 개발

이 게시물은 기본적으로 https://kr.vuejs.org/v2/guide/components.html 를 참조하여 공부하고 작성하였습니다.
제가 겪는 시행착오를 통해, 누군가에게 도움이 되기를 바랍니다.


컴포넌트는 Vue의 가장 강력한 기능입니다. 제게는 Class의 개념과 매우 유사하게 느껴지는데요, 재사용 가능한 코드를 캡슐화하는데 도움이 된다고 합니다. 

1. 컴포넌트 전역 등록

"일단 등록되면, 컴포넌트는 인스턴스의 템플릿에서 커스텀 엘리먼트,<my-component></my-component>로 사용할 수 있습니다. 루트 Vue 인스턴스를 인스턴스화하기 전에 컴포넌트가 등록되어 있는지 확인하십시오." 라고 적혀있습니다. 그런데, 제가 생각하는 기본적인(?) 프로그래밍에서 처럼, 루트를 먼저 생성하고 등록해야하는 것이 아닌가 생각을 했었습니다. 그런데, 루트 '인스턴스' 생성이기 때문에, 그 전에 '등록'을 거쳐 설정을 완료 하고, 인스턴스를 생성하는 과정으로 순서를 이해해야 하는 것 같습니다~


<div id="example">
<my-component></my-component>
</div>


// 등록
Vue.component('my-component', {
template: '<div>사용자 정의 컴포넌트 입니다!</div>'
})

// 루트 인스턴스 생성
new Vue({
el: '#example'
})

위와 같이 입력하는 것이, 아래와 같이 입력하는 것과 동일한 결과를 가져옵니다!!

<div id="example">
<div>사용자 정의 컴포넌트 입니다!</div>
</div>

어떤 느낌인지 감이 오시죠?! 

2. 컴포넌트 지역 등록

"모든 컴포넌트를 전역으로 등록 할 필요는 없습니다. 컴포넌트를 components 인스턴스 옵션으로 등록함으로써 다른 인스턴스/컴포넌트의 범위에서만 사용할 수있는 컴포넌트를 만들 수 있습니다" 뭔가 private의 느낌이 나지 않나요? 아무련, 저는 이게 더 깔끔하고 좋은 것 같습니다- 저는 구분지어 보기 위해 출력 결과에 'Child'라는 것을 추가해 보았습니다.

var Child = {
template: '<div>Child 사용자 정의 컴포넌트 입니다!</div>'
}

new Vue({
// ...
components: {
// <my-component> 는 상위 템플릿에서만 사용할 수 있습니다.
'my-component': Child
}
})

3. 컴포넌트의 데이터는 함수

컴포넌트의 data 는 함수여야 합니다. 이를 어길시 발생하는 문제를 확인하기 위하여 아래의 코드가 적혀있습니다. 어떠한 에러가 어디서 나는지 확인하기 위해 실행해보았습니다. 

Vue.component('my-component', {
template: '<span>{{ message }}</span>',
data: {
message: 'hello'
}
})

음... 에러메세지는 뜨지 않았습니다. 그러나, 나타나는 결과도 없었습니다.
component를 사용할 때에는 data내에 message를 그냥 저렇게 넣는 것이 아니라 아래에 나온 코드 처럼 함수의 형태로 사용해야 하는 것이 법칙인가 봅니다..! 이해 안시키고 그냥 쓰라하는거 싫어하는데.. 주입식 교육... 

var data = { counter: 0 }

Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
// 데이터는 기술적으로 함수이므로 Vue는 따지지 않지만
// 각 컴포넌트 인스턴스에 대해 같은 객체 참조를 반환합니다.
data: function () {
return data
}
})

new Vue({
el: '#example-2'
})


<div id="example-2">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>

데이터가 아니라 함수여야만 하는 이유를(강제적으로 그렇게 한 이유를?) 설명하기 위해서, 스위치를 이용한 예제를 보여줍니다. 그러면서 return 해주는 것으로- 데이터가 독립적으로 작동한다... 는 것을 보여주는데- 제게는 아직 이게 그렇게 효율적인 것인가 잘 이해가 되지는 않습니다. 제가 작동 원리를 잘 이해 못해서 그런 것 같습니다...

data: function () {
return {
counter: 0
}
}

data 부분을 위 처럼 수정을 하면! 스위치가 독립적으로 작동합니다. 3개의 스위치가 simple-counter를 통해서 나타납니다. v-on:click을 통해서- 클릭될 때마다 counter가 1씩 증가하는 구조로 짜여져 있습니다. 그런데, data가 그저 갖고 있는 값을 return 하는 것이었다면, 아래의 코드는 counter: 0으로 하여 return 하는 것인데...- 음... 단순히 이걸로 "각각 고유한 내부 상태가 있습니다." 라고 하는 것은 저에겐 조금 이해가 되지 않습니다. 내부적인 부분에서 무엇인가 더 있는 것인지... 이해력이 부족한 저는 웁니다.

4. 컴포넌트의 부모/자식 관계 (Props/ Events)

"Vue.js에서 부모-자식 컴포넌트 관계는 props는 아래로, events 위로 라고 요약 할 수 있습니다. 부모는 props를 통해 자식에게 데이터를 전달하고 자식은 events를 통해 부모에게 메시지를 보냅니다." 컴포넌트는 정말 클래스 같지 않은가요?!

공식 사이트에 있는 소스를 조금 변경하였습니다. 그 자체로 실행되는 것은 아니기도 하고, 좀 더 학습의 의미가 있을 것 같아서- 같이 생각해볼 수 있는 부분이 생기는 것 같아서 그렇습니다...

Vue.component('child', {
// props 정의
props: ['message'],
// 데이터와 마찬가지로 prop은 템플릿 내부에서 사용할 수 있으며
// vm의 this.message로 사용할 수 있습니다.
template: '<span>{{ message }}</span>'
})

//루트 인스턴스 생성
new Vue({
el:'#example',
components: {
'my-component': Child
}
})

var data = { counter: 0 }

Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
// 데이터는 기술적으로 함수이므로 Vue는 따지지 않지만
// 각 컴포넌트 인스턴스에 대해 같은 객체 참조를 반환합니다.
data: function () {
return {
counter: 0
}
}

})

new Vue({
el: '#example-2'
})

아주 길어졌죠..? 간단히 나눠보면, Vue에서 child component를 정의하는 부분과, Vue 루트 인스턴스 #example을 생성하는 부분, 그리고 simple-counter component를 정의하는 부분과, Vue 인스턴스 example2를 생성하는 부분이죠- 순서가 매우 중요합니다!

 <div id="example">
<my-component></my-component>
<child message="뭔가 이상한데"></child>
<simple-counter></simple-counter>
</div>

<div id="example-2">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<child message="안녕하세요!"></child>
</div>

그리고 HTML에는 이렇게 작성을 하였는데요, example과 example-2로 나누어서 simple counter와 child에서 props로 message을 전달하는 것을 수행했습니다. 그리고 이 결과는 아래와 같았습니다.

html 기준으로 본다면, child는 example과 example-2에서 모두 실행된 것을 보실 수 있습니다. js 파일에서 보시면 example과 example-2 엘리먼트를 생성하기 전에 child를 정의해서 두 엘리먼트에 모두 적용된 것으로 생각됩니다.
반면에, simple-counter는 js 파일에서보시면 example 엘리먼트 생성 후, example-2 엘리먼트 생성 전에 정의하였습니다. 따라서 example-2에만 적용이 되었고- html파일에서 example-2에서만 적용이 되어 나타나는 것을 결과로 보실 수가 있는 거죠!

말이 조금 복잡했나요..? 아무련! 순서가 매우 중요한 것임을 알려드리기 위해서 코드를 섞어서 나열해 실행시켜보았습니다. 사실 여기서 중요한 것은 child가 props로 message를 전달해서 수행한다는 것이겠죠?

5. 동적 props

"정규 속성을 표현식에 바인딩하는 것과 비슷하게, v-bind를 사용하여 부모의 데이터에 props를 동적으로 바인딩 할 수 있습니다. 데이터가 상위에서 업데이트 될 때마다 하위 데이터로도 전달됩니다." 라고 아주 잘 설명 되어있어서- 소스코드를 적용해서 실행했더니 아무 것도 안나오더라구요... 그래서 그 직전의 "camelCase vs. kebab-case" 내용을 간단히 읽고만 넘어갔는데- 이 부분의 코드를 활용하는 것 같아서 둘 다 합쳐서 해보려 합니다.. 흠... 잘 작동하지 않습니다...

일단, camelCase vs. kebab-case에 대해 알아봅시다.
두 단어가 연결되어 표현될 때, 소문자, 대문자 -을 이용해 어떻게 표현할지에 대한 case convention이라고 보시면 됩니다.
camelCase는 소문자 + 대문자, kebab-case는 소문자 + '-' + 대문자로 이루어져있습니다!

PascalCase
camelCase
snake_case
lisp-case (=spinal-case, kebab-case)
Train-Case

참조 (https://stackoverflow.com/questions/11273282/whats-the-name-for-hyphen-separated-case/12273101)

그래서 이 이야기를 왜 vue에서 설명하냐? JavaScript는 camelCase, HTML은 kebab-case입니다!


Vue.component('child', {
// JavaScript는 camelCase
props: ['myMessage'],
template: '<span>{{ myMessage }}</span>'
})

<!-- HTML는 kebab-case -->
<child my-message="안녕하세요!"></child>

이렇게 js 파일에서의 myMessage와 HTML 파일에서의 my-message가 다른 case로 쓰여졌지만-프레임워크 차원에서 자동으로 연결해줍니다! 실제로 실행해보셔서 확인해보실 수 있으시구요- (위에서 실행시킬 때 처럼 vue 인스턴스 생성 이름과- child의 div id를 일치 시켜줬습니다. 이 코드만 독립적으로 실행이 되지는 않을 것 같습니다.)

우리가 낙타와 케밥으로 주제를 변경했던 이유는, 동적 props 예제 코드가 제대로 작동하지 않았기 때문인데요- 문제의 코드는 바로 이것입니다.

<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>

조금씩 수정하며 다시 해보아도 작동하지 않습니다. 안됩니다.... 혹시 해결 방법을 아시는 분은 알려주시면 감사드리겠습니다.

덧글

댓글 입력 영역



통계 위젯 (화이트)

09
34
1528