Vue.js Component

Vue 컴포넌트

Vue.js는 컴포넌트들을 조합하여 전체 애플리케이션을 작성한다. 컴포넌트들은 부모-자식 관계로 트리 구조를 형성하는데, 부모 컴포넌트가 자식 컴포넌트를 포함하는 형태를 취한다.

컴포넌트들은 속성(Props) 을 통해서 자식 컴포넌트로 정보를 전달한다. 주로 부모 컴포넌트에서 자식 컴포넌트로의 단방향으로만 전달한다. (양방향도 가능하지만 너무 복잡해져서 fail… 자주 쓰지 않는다)

부모 컴포넌트는 자식 컴포넌트로 Props (속성)을 전달하고, 자식 컴포넌트는 부모 컴포넌트로 이벤트를 발신한다. 자식 컴포넌트에서 사용자 정의 이벤트를 정의하고, 이벤트를 발생시키면 부모 컴포넌트에서 이벤트 핸들러 메서드를 호출하도록 작성한다. 이렇게, 속성 전달이벤트 발신이 부모-자식 간 상호 작용을 일으키는 방법이다.

data, methods, computed, watched 속성과 같은 대부분의 Vue 인스턴스의 옵션을 컴포넌트 수준에서도 사용할 수 있는데, 그 중 data 옵션은 각 컴포넌트의 Local State를 관리하기 위한 용도로만 사용한다. 만약, data 옵션을 단순한 객체 값으로 작성하게 된다면, 객체가 참조형 값이기 때문에 모두 동일한 값을 참조하게 된다. 따라서, 컴포넌트에서의 data 옵션반드시 함수로 작성하고, 함수 내부에서 객체를 리턴하도록 작성해야한다는 주의점이 있다.

컴포넌트의 작성

  • 컴포넌트를 작성하는 메소드
    • Vue.component(tagname, options)
      • tagname : 컴포넌트를 사용할 태그명 (kebob casing을 따른다)
      • options : 컴포넌트에서 렌더링 할 template 지정
  • 기본 틀
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>06-01</title>
  <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
</head>

<body>

</body>
<script type="text/javascript">

</script>
</html>

  • 컴포넌트 코드 추가 (Vue 컴포넌트의 template 옵션에 인라인 템플릿 문자열 사용)
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>06-01</title>
  <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
</head>
<script type="text/javascript">
  // 인라인 템플릿
  Vue.component('hello-component', {
    template: '<div>hello world</div>'
  })
</script>
<body>

</body>
<script type="text/javascript">

</script>
</html>

  • 작성한 컴포넌트의 사용
    • 등록한 태그명을 사용
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>06-01</title>
  <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
</head>
<script type="text/javascript">
  // 인라인 템플릿
  Vue.component('hello-component', {
    template: '<div>hello world</div>'
  })
</script>
<body>
  <div id="app">
      <hello-component></hello-component>
      <hello-component></hello-component>
      <hello-component></hello-component>
  </div>
</body>
<script type="text/javascript">
  Vue.config.devtools = true; // chrome 브라우저에 설치한 Vue devtools 사용을 위함

  var v = new Vue({
    el : '#app'
  })
</script>
</html>

  • 템플릿 문자열 분리
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>06-01</title>
  <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
</head>
<template id="helloTemplate">
  <div>hello world !!! </div>
</template>
<script type="text/javascript">
  // 인라인 템플릿
  Vue.component('hello-component', {
    template: '#helloTemplate'
  })
</script>
<body>
  <div id="app">
      <hello-component></hello-component>
      <hello-component></hello-component>
      <hello-component></hello-component>
  </div>
</body>
<script type="text/javascript">
  Vue.config.devtools = true; // chrome 브라우저에 설치한 Vue devtools 사용을 위함

  var v = new Vue({
    el : '#app'
  })
</script>
</html>

  • template 태그를 script 태그로 변경
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>06-01</title>
  <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
</head>
<script type="text/x-template" id="helloTemplate">
  <div>hello world !!! </div>
</script>
<script type="text/javascript">
  // 인라인 템플릿
  Vue.component('hello-component', {
    template: '#helloTemplate'
  })
</script>
<body>
  <div id="app">
      <hello-component></hello-component>
      <hello-component></hello-component>
      <hello-component></hello-component>
  </div>
</body>
<script type="text/javascript">
  Vue.config.devtools = true; // chrome 브라우저에 설치한 Vue devtools 사용을 위함

  var v = new Vue({
    el : '#app'
  })
</script>
</html>

DOM 템플릿 구문 작성 시 주의사항

HTML 요소들은 자식 요소로 포함시킬 수 있는 요소들이 정해져 있는 경우가 있고, 이 사항을 브라우저가 구문 분석을 수행한다. 이러한 경우, Vue 컴포넌트가 사용되면 때때로 오류가 발생하기도 한다.

  • 오류가 발생하는 예제
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>06-06</title>
  <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
</head>

<script type="text/javascript">
  // 인라인 템플릿
  Vue.component('option-component', {
    template: '<option>hello</option>'
  })
</script>
<body>
  <div id="app">
    <select>
      <option-component></option-component>
      <option-component></option-component>
    </select>
  </div>
</body>
<script type="text/javascript">
  Vue.config.devtools = true; // chrome 브라우저에 설치한 Vue devtools 사용을 위함

  var v = new Vue({
    el : '#app'
  })
</script>
</html>

  • <select> 안에서 <option-template> 태그를 사용할 수 있다는 것이 브라우저에 등록되어 있지 않음

  • 브라우저는 이 태그들을 구문 분석하는 작업을 먼저 수행한 후 Vue 컴포넌트를 렌더링 함
  • 구문 분석 단계에서 DOM 요소가 올바르지 않다고 판단하기 때문에, 제대로 렌더링하지 못하는 문제가 발생함
  • 이 문제를 해결하기 위해 is 특성을 사용
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>06-06</title>
  <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
</head>

<script type="text/javascript">
  // 인라인 템플릿
  Vue.component('option-component', {
    template: '<option>hello</option>'
  })
</script>
<body>
  <div id="app">
    <select>
      <option is="option-component"></option>
      <option is="option-component"></option>
    </select>
  </div>
</body>
<script type="text/javascript">
  Vue.config.devtools = true; // chrome 브라우저에 설치한 Vue devtools 사용을 위함

  var v = new Vue({
    el : '#app'
  })
</script>
</html>

  • 단일 파일 컴포넌트 (.vue 확장자를 사용하는 컴포넌트 파일)을 작성하는 경우에는 is 특성을 사용하지 않아도 됨
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>06-06</title>
  <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
</head>

<script type="text/javascript">
  // 인라인 템플릿
  Vue.component('option-component', {
    template: '<option>hello</option>'
  })
</script>
<script type="text/x-template" id="selectTemplate">
  <select>
    <option-component></option-component>
    <option-component></option-component>
  </select>
</script>
<script type="text/javascript">
  Vue.component('select-component', {
    template : '#selectTemplate'
  })
</script>
<body>
  <div id="app">
    <select>
      <select-component></select-component>
  </div>
</body>
<script type="text/javascript">
  Vue.config.devtools = true; // chrome 브라우저에 설치한 Vue devtools 사용을 위함

  var v = new Vue({
    el : '#app'
  })
</script>
</html>

props와 event

1547170367521

부모 컴포넌트와 자식 컴포넌트 사이에 속성(Props)과 이벤트(Event)를 이용해서 상호 작용하고 서로 통신한다. Vue 컴포넌트들이 부모-자식 관계로 형성되었을 때 각 컴포넌트 내부의 데이터는 캡슐화되기 때문에 다른 컴포넌트나 앱에서 접근할 수 없다. 따라서, 부모 컴포넌트에서 자식 컴포넌트로 필요한 정보를 전달하기 위해선, props를 이용해야 한다. ( 부모 –> 자식 단방향 전달만)

  • props를 이용한 정보 전달
    • vue 컴포넌트를 정의할 때 props라는 옵션을 작성하고 props명을 배열로 나열
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>06-12</title>
  <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
</head>
<template id="listTemplate">
  <li></li>
</template>

<script type="text/javascript">
  // 인라인 템플릿
  Vue.component('list-component', {
    template: '#listTemplate',
    props: [ 'message' ]
  })
</script>
<body>
  <div id="app">
    <ul>
      <list-component message="Hello"></list-component>
      <list-component message="씬짜오"></list-component>
      <list-component message="니하오마"></list-component>
    </ul>
  </div>
</body>
<script type="text/javascript">
  Vue.config.devtools = true; // chrome 브라우저에 설치한 Vue devtools 사용을 위함

  var v = new Vue({
    el : '#app'
  })
</script>
</html>
  • 컴포넌트의 속성을 표기할 때 주의사항
    • 컴포넌트 작성 시 속성명을 부여할 때, camel casing을 이용했다면, 속성명을 사용할 정보를 전달할 때는 kebob casing을 이용해야 함
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>06-12</title>
  <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
</head>
<template id="listTemplate">
  <li></li>
</template>

<script type="text/javascript">
  // 인라인 템플릿
  Vue.component('list-component', {
    template: '#listTemplate',
    props: [ 'myMessage' ]
  })
</script>
<body>
  <div id="app">
    <ul>
      <list-component my-message="Hello"></list-component>
      <list-component my-message="씬짜오"></list-component>
      <list-component my-message="니하오마"></list-component>
    </ul>
  </div>
</body>
<script type="text/javascript">
  Vue.config.devtools = true; // chrome 브라우저에 설치한 Vue devtools 사용을 위함

  var v = new Vue({
    el : '#app'
  })
</script>
</html>

  • 속성에 대해 엄격한 유효성 검증이 필요하다면 배열이 아닌 객체 형태를 사용할 수도 있음
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>06-12</title>
  <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
</head>
<template id="listTemplate">
  <li></li>
</template>

<script type="text/javascript">
  // 인라인 템플릿
  Vue.component('list-component', {
    template: '#listTemplate',
    props: {
      message:  {
        type: String,
        default: '안녕하세요'
      },
      count: {
        type: Number,
        required: true
      }
    }
    ,
    count : {
      type: Number,
      required : true
    }
  })
</script>
<body>
  <div id="app">
    <ul>
      <list-component message="Hello" count="100"></list-component>
      <list-component message="씬짜오" count="21"></list-component>
      <list-component message="니하오마"></list-component>
      <list-component count="1000"></list-component>
    </ul>
  </div>
</body>
<script type="text/javascript">
  Vue.config.devtools = true; // chrome 브라우저에 설치한 Vue devtools 사용을 위함

  var v = new Vue({
    el : '#app'
  })
</script>
</html>
  • count 속성이 숫자형인데, 리터럴로 속성을 전달하여 문자열 값으로 그대로 전달됨
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>06-12</title>
  <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
</head>
<template id="listTemplate">
  <li></li>
</template>

<script type="text/javascript">
  // 인라인 템플릿
  Vue.component('list-component', {
    template: '#listTemplate',
    props: {
      message:  {
        type: String,
        default: '안녕하세요'
      },
      count: {
        type: Number,
        required: true
      }
    }
    ,
    count : {
      type: Number,
      required : true
    }
  })
</script>
<body>
  <div id="app">
    <ul>
      <list-component message="Hello" v-bind:count="100"></list-component> <!--count속성은 숫자형인데 리터럴로 속성을 전달하여서 문자열 값으로 전달됨, v-bind 디렉티브를 사용해서 해결-->
      <list-component message="씬짜오" :count="21"></list-component>
      <list-component message="니하오마"></list-component> <!--count 속성이 필수입력사항인데, 아예 전달하지 않으면 렌더링에 문제 없음-->
      <list-component :count="1000"></list-component>
    </ul>
  </div>
</body>
<script type="text/javascript">
  Vue.config.devtools = true; // chrome 브라우저에 설치한 Vue devtools 사용을 위함

  var v = new Vue({
    el : '#app'
  })
</script>
</html>
  • v-bind 디렉티브를 이용하여 해결

  • 속성으로 전달할 값이 배열이나 객체인 경우

    • default value를 부여하려면 반드시 함수를 사용해야 함
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>06-12</title>
  <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
</head>
<template id="listTemplate">
  <li></li>
</template>

<script type="text/javascript">
  // 인라인 템플릿
  Vue.component('list-component', {
    template: '#listTemplate',
    props: {
      message:  {
        type: String,
        default: '안녕하세요'
      },
      count: {
        type: Number,
        required: true
      },
      countries: {
        type: Array,
        // default value가 배열이므로 기본값 부여 시 함수의 리턴값으로 부여하도록 함
        default: function() {
          return ['대한민국'];
        }
      }
    }
    ,
    count : {
      type: Number,
      required : true
    }
  })
</script>
<body>
  <div id="app">
    <ul>
      <list-component message="Hello" v-bind:count="100" v-bind:countries="['미국','영국','호주']"></list-component> <!--count속성은 숫자형인데 리터럴로 속성을 전달하여서 문자열 값으로 전달됨, v-bind 디렉티브를 사용해서 해결-->
      <list-component message="씬짜오" :count="21" :countries="['베트남']"></list-component>
      <list-component message="니하오마" :countries="['중국', '타이완']"></list-component> <!--count 속성이 필수입력사항인데, 아예 전달하지 않으면 렌더링에 문제 없음-->
      <list-component :count="1000"></list-component>
    </ul>
  </div>
</body>
<script type="text/javascript">
  Vue.config.devtools = true; // chrome 브라우저에 설치한 Vue devtools 사용을 위함

  var v = new Vue({
    el : '#app'
  })
</script>
</html>