본문 바로가기

Vue.js

컴포넌트


컴포넌트가 무엇인가요?


컴포넌트 : 객체지향 언어에서 class와 같은 개념.

Vue 컴포넌트 : 작은 Vue 인스턴스를 필요할 때마다 재사용한다는 개념. 기본 HTML 엘리먼트를 확장하여 재사용 가능한 코드를 캡슐화하는 데 도움이 된다. 




컴포넌트 사용하기


# 전역으로 사용

: Vue 객체의 component 메서드 사용. 전역으로 생성했기때문에, 모든 Vue 인스턴스에서 사용할 수 있다.

component 메서드는 2가지 인자 받음. 컴포넌트의 이름과 컴포넌트 동작에 필요한 옵션.

  꼭 루트 Vue 인스턴스를 인스턴스화하기 전에 컴포넌트가 등록되어 있는지 확인하자.

예제

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

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

// 루트 인스턴스 생성
new Vue({
el: "#example"
});
</script>




# 지역으로 사용

:  components 인스턴스 옵션에 컴포넌트 생성 옵션 추가.

예제


<body>
<div id="example2"><my-component2></my-component2></div>
</body>

<script>
var Child = {
template: "<div>지역적 사용자 정의 컴포넌트 입니다!</div>"
};

new Vue({
el: "#example2",
components: {
// <my-component2> 는 상위 템플릿에서만 사용할 수 있습니다.
"my-component2": Child
}
});
</script>





# DOM 템플릿 구문 분석 경고 - 브라우저에서 띄우는 경고


DOM을 템플릿으로 사용할 때 (즉, 여지껏 예제하던데로 el옵션으로 html 가르킬때), 

제한이 있는 엘리먼트가 있는 사용자 지정 컴포넌트를 사용하면 다음과 같은 문제가 발생할 수 있다.


*문제


<table>
<my-row>...</my-row>
</table>


위 코드는 html에서 에러가 난다. 이유는 <table>엘리멘트에는 <th>,<tr>,<td>등이 있다. 

하지만 위와 같이 <table>엘리먼트안에 모르는 태그(<my-row>)가 있으니 에러가 난다. 

위 문제를 해결하는 방법은  is 특수 속성을 사용하는 것이다.


*해결(is특수 속성으로)


<table>
    <tr is="my-row"></tr>
</table>


위 코드는 에러가 안난다. 이유는 <table>엘리먼트안에 <tr>태그가 있다. 

하지만 html 입장에서 ' is="my-row" ' 이것은 단순히 문자로 찍히고 그 후에 vue엔진이 렌더링하여 "my-row"를 컴포넌트화 한다. 

여기서 렌더링이란 태그나 표현식에 대해서 vue엔진이 해석을하여 동작하는것이다.


다음 소스 중 하나에 포함되면 문자열 템플릿(여기서 템플릿은 간단하게 html영역이라고 생각하면된다.)을 사용하는 경우에는 이러한 제한 사항이 적용되지 않는다. : 즉, 아래와 같은 상황은 is 안써도 에러가 안난다.

1. <script type="text/x-template">

2. JavaScript 인라인 템플릿 문자열

3. .vue 컴포넌트

따라서 가능한 경우 항상 문자열 템플릿을 사용하는 것이 좋다.




# data는 반드시 함수여야 한다.

즉, 컴포넌트에서 data는 반드시 함수여야 한다.

제 

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

<script>
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"
});
</script>


결과  


위 코드처럼 data는 함수여야 한다. 또 컴포넌트는 new로 생성하는 것이 아니라 메모리를 공유한다. 

그래서 위 코드는 세 개의 컴포넌트 인스턴스가 모두 같은 data 객체를 공유하므로 위 결과처럼 하나의 카운트를 중가 시키면 모두 증가한다. 

따라서 이 문제를 해결하려면 새로운 데이터 객체를 반환하여야 한다.


예제

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

<script>

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


new Vue({
el: "#example-2"
});
</script>


결과

이제 모든 카운터에는 각각 고유한 내부 상태가 있다.




# 컴포넌트 작성 


컴포넌트는 부모-자식 관계에서 가장 일반적으로 함께 사용하기 위한 것이다.

부모는 자식에게 데이터를 전달해야 할 수도 있으며, 자식은 자신에게 일어난 일을 부모에게 알릴 필요가 있다.

그러나 부모와 자식이 명확하게 정의된 인터페이스를 통해 가능한한 분리된 상태로 유지하는 것도 매우 중요하다.

 이렇게하면 각 컴포넌트의 코드를 상대적으로 격리 할 수 있도록 작성하고 추론할 수 있으므로 유지 관리가 쉽고 잠재적으로 쉽게 재사용 할 수 있다.


Vue.js에서 부모-자식은 단방향이다.


Vue.js에서 부모-자식 컴포넌트 관계는 props는 아래로, events 위로 라고 요약 할 수 있다. 

부모는 props를 통해 자식에게 데이터를 전달하고 자식은 events를 통해 부모에게 메시지를 보낸다. 

props는 부모가 자식한테 파라미터로 부모의 데이터를 자식에게 전달한다.

events는 자식이 부모한테 이벤트를 쏜다. 이벤트 핸들링 방식이다. 


예를들어 자식이 부모의 메소드를 사용하고 싶을 때, 우리는 흔히 parent.save()를 생각한다.

하지만 이렇게 하면 자식이 부모에 종속되니까(child는 parent의 save()만 할 수 있음) 재사용(유지보수)에 있어서 바람직하지 않다. 

따라서 child에서 parent에게 event를 쏜다(emit한다). 그러면 parent가 child의 emit을 감지하고 parent가 해당하는 데이터를 child에게 파라미터로 props한다.




Props



# Props로 데이터 전달하기


모든 컴포넌트 인스턴스에는 자체 격리 된 범위 가 있다. 

즉, 하위 컴포넌트의 템플릿에서 상위 데이터를 직접 참조 할 수 없으며 그렇게 해서는 안된다.

 데이터는 props 옵션을 사용하여 하위 컴포넌트로 전달 될 수 있다.

prop는 상위 컴포넌트의 정보를 전달하기위한 사용자 지정 특성

하위 컴포넌트는props 옵션을 사용하여 수신 할 것으로 기대되는 props를 명시적으로 선언해야한다.


<body>
<div id="ex">
<pro-child message="하나"></pro-child>
<pro-child message="둘"></pro-child>
<pro-child message="셋"></pro-child>
</div>
</body>

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




# camelCase vs kebab-case

camelCase : camel은 낙타다. 낙타의 혹처럼 앞은 소문자 뒤는 대문자로 쓴다.

kebab-case : kebab은 막대기에 꽂아 먹는 것이다. 그래서 문자 사이에 하이픈(-)을 쓴다.

여기서 중요한 규칙이 있다. 

props는 camelCase로, HTML 태그에는 kebab-case를 사용해야 한다.

문자열 템플릿을 사용하는 경우에도 이 제한이 적용되지 않습니다.


예제


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

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





# 동적 Props


 v-bind를 사용하여 부모의 데이터에 props를 동적으로 바인딩 할 수 있다. 데이터가 상위에서 업데이트 될 때마다 하위 데이터로도 전달된다. 즉, 부모데이터를 자식에게 넘겨주려면 v-bind를 쓰라!

v-bind가 감시해서 알려준다.


예제

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

//동적 props
Vue.component("child", {
// props 정의
props: ["myMessage"],
// 데이터와 마찬가지로 prop은 템플릿 내부에서 사용할 수 있으며
// vm의 this.message로 사용할 수 있습니다.
template: "<span>{{ myMessage }}</span>"
});
new Vue({
el: "#ex2",
data: {
parentMsg: "부모가 가지고 있는 데이터"
}
});

결과



# 리터럴 vs 동적


흔히 범하는 실수는 리터럴 구문을 사용하여 숫자를 전달하려고 시도하는 것.



<!-- 이것은 일반 문자열 "1"을 전달합니다. -->
<comp some-prop="1"></comp>


실제 JavaScript 숫자를 전달하려면 값이 JavaScript 표현식으로 평가되도록 v-bind를 사용해야한다.



<!-- 이것은 실제 숫자로 전달합니다. -->
<comp v-bind:some-prop="1"></comp>





# 단방향 데이터 흐름


모든 props는 하위 속성과 상위 속성 사이의 단방향 바인딩을 형성한다.

상위 속성이 업데이트되면 하위로 흐르게 되지만 그 반대는 안된다.


1. prop의 초기 값을 초기 값으로 사용하는 로컬 데이터 속성을 정의 하라. 

   즉, 부모로부터 받은 prop 초기 값들을 복사하여 사용하라. 이렇게 하면 부모의 데이터에 영향을 끼치지 않는다. 


2. prop 값으로 부터 계산된 속성을 정의 한다.

자바 스크립트의 객체와 배열은 참조로 전달되므로 prop가 배열이나 객체인 경우 하위 객체 또는 배열 자체를 부모 상태로 변경하면 부모 상태에 영향을 줍니다.

v-model은 양방향 디렉티브이다. 그래서 v-model에서 input과 textarea는 양방향으로 쓰인다. 따라서 자식이 props의 초기 데이터를 사용할 때 부모의 데이터에 영향을 끼치지 않도록 주의하여야 한다.




# Prop 검증 


컴포넌트가 받는 중인 prop에 대한 요구사항을 지정할 수 있다. 요구사항이 충족 되지 않으면 Vue에서 경고를 내보낸다.  

props를 문자열 배열로 정의하는 대신 유효성 검사 요구사항이 있는 객체를 사용할 수 있다.


예제


Vue.component('example', {
props: {
// 기본 타입 확인 (`null` 은 어떤 타입이든 가능하다는 뜻입니다)
propA: Number,
// 여러개의 가능한 타입
propB: [String, Number],
// 문자열이며 꼭 필요합니다
propC: {
type: String,
required: true
},
// 숫자이며 기본 값을 가집니다
propD: {
type: Number,
default: 100
},
// 객체/배열의 기본값은 팩토리 함수에서 반환 되어야 합니다.
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 사용자 정의 유효성 검사 가능
propF: {
validator: function (value) {
return value > 10
}
}
}
})





Props가 아닌 속성


# 존재하는 속성 교체/병합


props 지정된 것 외에는 복사해주고, 중복되면 알아서 합쳐준다.(가이드 설명이 길고 복잡하여 나름대로 정의한 한줄..)




#v-on 을 이용한 사용자 지정 이벤트 


모든 Vue 인스턴스는 다음과 같은 이벤트 인터페이스를 구현

  • $on(eventName)을 사용하여 이벤트를 감지 하십시오.
  • $emit(eventName)을 사용하여 이벤트를 트리거 하십시오.

부모 컴포넌트는 자식 컴포넌트가 사용되는 템플릿에서 직접 v-on 을 사용하여 자식 컴포넌트에서 보내진 이벤트를 청취할 수 있다 즉, 부모는 자식에게 귀를 귀울이고 있음(v-on).

$on은 자식에서 호출한 이벤트는 감지하지 않는다. v-on을 템플릿에 반드시 지정해야 한다.


예제

<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>

//v-on을 이용한 사용자 지정 이벤트
Vue.component("button-counter", {
template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
data: function() {
return {
counter: 0
};
},
methods: {
incrementCounter: function() {
this.counter += 1;
this.$emit("increment");
}
}
});

new Vue({
el: "#counter-event-example",
data: {
total: 0
},
methods: {
incrementTotal: function() {
this.total += 1;
}
}
});


                                                

코드 설명 : 

<script>

부모(new Vue로 이를 루트 엘리먼트라고 부른다.)는 incrementTotal 메소드가 있다. 이 메소드는 total 데이터를 1씩 증가한다.

자식(new Vue가 최상위고 나머지 컴포넌트는 하위다.)은 incremetCount 메소드가 있다. 이 메소드는 count 데이터를 1씩 증가한다. 그리고 부모에게 increment라는 이벤트를 emit한다.


<html>

button-counter라는 컴포넌트(자식)에서 부모가 increment라는 이벤트를 들으면(v-on) incrementTotal 메소드를 실행하라.



'Vue.js' 카테고리의 다른 글

Router  (0) 2020.01.20
Reactivity  (0) 2020.01.20
폼 입력 바인딩  (0) 2019.01.15
이벤트 렌더링  (0) 2019.01.15
리스트 렌더링  (0) 2019.01.14