ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [가이드] 컴포넌트 기초(1) - 필수요소 | Vue
    Front-end/Vue.js 2020. 8. 18. 16:48
    반응형

    컴포넌트가 무엇인가요?

    컴포넌트는 Vue의 가장 강력한 기능 중 하나입니다. 기본 HTML 엘리먼트를 확장하여 재사용 가능한 코드를 캡슐화하는 데 도움이 됩니다. 상위 수준에서 컴포넌트는 Vue의 컴파일러에 의해 동작이 추가된 사용자 지정 엘리먼트입니다. 경우에 따라 특별한 is 속성으로 확장된 원시 HTML 엘리먼트로 나타날 수도 있습니다.

     

    Vue 컴포넌트는 Vue 인스턴스이기도 합니다. 그러므로 모든 옵션 객체를 사용할 수 있습니다. (루트에만 사용하는 옵션은 제외) 그리고 같은 라이프사이클 훅을 사용할 수 있습니다.

     

     

    컴포넌트 사용하기

    전역 등록

    전역 컴포넌트를 등록하려면, Vue.component(tagName, options)를 사용합니다.

    Vue.component('my-component', {
      // 옵션
    })

     

    Vue는 사용자 지정 태그 이름에 대해 [W3C 규칙](http://www.w3.org/TR/custom-elements/#concepts)을 적용하지 않습니다. 모두 소문자이어야 하고 하이픈을 포함해야 합니다. 이 규칙을 따르는 것이 좋습니다.

     

    일단 등록되면, 컴포넌트는 인스턴스의 템플릿에서 커스텀 엘리먼트, <my-component></my-component>로 사용할 수 있습니다. 루트 Vue 인스턴스를 인스턴스화 하기 전에 컴포넌트가 등록되어 있는지 확인하십시오. 전체 예제는 다음과 같습니다.

    <div id="example">
      <my-component></my-component>
    </div>
    
    <script>
      // 컴포넌트 등록
      Vue.component('my-component', {
        template: '<div>사용자 정의 컴포넌트 입니다!</div>'
      })
    
      // 루트 인스턴스 생성
      new Vue({
        el: '#example'
      })
    </script>

     

    지역 등록

    모든 컴포넌트를 전역으로 등록할 필요는 없습니다. 컴포넌트를 components 인스턴스 옵션으로 등록함으로써 다른 인스턴스/컴포넌트의 범위에서만 사용할 수 있는 컴포넌트를 만들 수 있습니다:

    var Child = {
      template: '<div>사용자 정의 컴포넌트 입니다!</div>'
    }
    
    new Vue({
      // ...
      components: {
        // <my-component> 는 상위 템플릿에서만 사용할 수 있습니다.
        'my-component': Child
      }
    })

     

    DOM 템플릿 구문 분석 경고

    DOM을 템플릿으로 사용할 때 (예 : el 옵션을 사용하여 기존 콘텐츠가 있는 엘리먼트를 마운트 하는 경우), Vue는 템플릿 콘텐츠만 가져올 수 있기 때문에 HTML이 작동하는 방식에 고유한 몇 가지 제한 사항이 적용됩니다. 이는 브라우저가 구문 분석과 정규화한 후에 작동합니다. 가장 중요한 것은 <ul>, <ol>, <table>과 <select>와 같은 일부 엘리먼트는 그 안에 어떤 엘리먼트가 나타날 수 있는지에 대한 제한을 가지고 있으며, <option>과 같이 특정 다른 엘리먼트 안에만 나타날 수 있습니다.

     

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

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

    사용자 지정 컴포넌트 <my-row>는 잘못된 콘텐츠가 되어, 결과적으로 렌더링 시 에러를 발생시킵니다. 해결 방법은 is 특수 속성을 사용하는 것입니다 :

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

    다음 중 하나에 포함된 문자열 템플릿을 사용하는 경우에는 이러한 제한 사항이 적용되지 않습니다. :

     

    • <script type="text/x-template">

    • JavaScript 인라인 템플릿 문자열

    • . vue컴포넌트

     

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

     

    data는 반드시 함수여야 합니다.

    Vue 생성자에 사용할 수 있는 대부분의 옵션은 컴포넌트에서 사용할 수 있습니다. 한 가지 특별한 경우가 있습니다. data는 함수여야 합니다. 실제로 이를 사용하는 경우에 :

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

    그러면 Vue는 중단하고 콘솔에서 경고합니다. data는 컴포넌트 인스턴스의 함수여야 합니다. 규칙이 존재하는 이유를 이해하는 것이 좋습니다. 따라서 다음과 같이 사용하십시오.

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

    세 개의 컴포넌트 인스턴스가 모두 같은 data 객체를 공유하므로 하나의 카운터를 증가시키면 모두 증가합니다! 대신 새로운 데이터 객체를 반환하여 이 문제를 해결합시다.

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

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

     

    컴포넌트 작성

    컴포넌트는 부모-자식 관계에서 가장 일반적으로 함께 사용하기 위한 것입니다. 컴포넌트 A는 자체 템플릿에서 컴포넌트 B를 사용할 수 있습니다. 그들은 필연적으로 서로 의사소통이 필요합니다. 부모는 자식에게 데이터를 전달해야 할 수도 있으며, 자식은 자신에게 일어난 일을 부모에게 알릴 필요가 있습니다. 부모와 자식이 명확하게 정의된 인터페이스를 통해 가능한 한 분리된 상태로 유지하는 것도 매우 중요합니다. 이렇게 하면 각 컴포넌트의 코드를 상대적으로 격리할 수 있도록 작성하고 추론할 수 있으므로 유지 관리가 쉽고 잠재적으로 쉽게 재사용할 수 있습니다.

     

    Vue.js에서 부모-자식 컴포넌트 관계는 props는 아래로, events 위로라고 요약할 수 있습니다. 부모는 props를 통해 자식에게 데이터를 전달하고 자식은 events를 통해 부모에게 메시지를 보냅니다. 어떻게 작동하는지 보겠습니다.

     

     

     

    Props

    Props로 데이터 전달하기

    모든 컴포넌트 인스턴스에는 자체 격리된 범위 가 있습니다. 즉, 하위 컴포넌트의 템플릿에서 상위 데이터를 직접 참조할 수 없으며 그렇게 해서는 안됩니다. 데이터는 props 옵션을 사용하여 하위 컴포넌트로 전달될 수 있습니다.

     

    prop는 상위 컴포넌트의 정보를 전달하기 위한 사용자 지정 특성입니다. 하위 컴포넌트는 props 옵션을 사용하여 수신할 것으로 기대되는 props를 명시적으로 선언해야 합니다.

    <!-- 일반 문자열을 다음과 같이 전달할 수 있습니다. -->
    <child message="안녕하세요!"></child>
    
    <script>
      Vue.component('child', {
        // props 정의
        props: ['message'],
        // 데이터와 마찬가지로 prop은 템플릿 내부에서 사용할 수 있으며
        // vm의 this.message로 사용할 수 있습니다.
        template: '<span>{{ message }}</span>'
      })
    </script>

     

    camelCase vs kebab-case

    HTML 속성은 대소 문자를 구분하지 않으므로 문자열이 아닌 템플릿을 사용할 때 camelCased prop 이름에 해당하는 kebab-case(하이픈 구분)를 사용해야 합니다.

    <!-- HTML는 kebab-case -->
    <child my-message="안녕하세요!"></child>
    
    <script>
      Vue.component('child', {
        // JavaScript는 camelCase
        props: ['myMessage'],
        template: '<span>{{ myMessage }}</span>'
      })
    </script>

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

     

    동적 Props

    정규 속성을 표현식에 바인딩하는 것과 비슷하게, v-bind를 사용하여 부모의 데이터에 props를 동적으로 바인딩할 수 있습니다. 데이터가 상위에서 업데이트될 때마다 하위 데이터로도 전달됩니다.

    <div id="ex3">
      <input type="text" v-model="parentText">
      <child :input-text="parentText"></child>
    </div>
    
    <script>
      Vue.component('child', {
        template: '<span>{{ inputText }}</span>',
        props: ['inputText']
      })
      
      new Vue({
        el: "#ex3",
        data: {
          parentText : ""
        }
      })  
    </script>

    객체의 모든 속성을 props로 전달하려면, 인자 없이 v-bind를 쓸 수 있습니다. (v-bind:prop-name 대신 v-bind).

    예를 들어 todo 객체가 있다면,

    <todo-item v-bind="todo"></todo-item>
    
    <script>
      todo: {
        text: 'Learn Vue',
        isComplete: false
      }
    </script>

    위 코드는 다음과 같은 동작 합니다.

    <todo-item v-bind:text="todo.text" v-bind:is-complete="todo.isComplete"></todo-item>

     

    리터럴 vs. 동적

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

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

    그러나 이것은 리터럴 prop이기 때문에 그 값은 실제 숫자가 아닌 일반 문자열 "1"로 전달됩니다. 실제 JavaScript 숫자를 전달하려면 값이 JavaScript 표현식으로 평가되도록 v-bind를 사용해야 합니다.

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

     

    단방향 데이터 흐름

    모든 props는 하위 속성과 상위 속성 사이의 단방향 바인딩을 형성합니다. 상위 속성이 업데이트되면 하위로 흐르게 되지만 그 반대는 안됩니다. 이렇게 하면 하위 컴포넌트가 실수로 부모의 상태를 변경하여 앱의 데이터 흐름을 추론하기 더 어렵게 만드는 것을 방지할 수 있습니다.

     

    일반적으로 prop을 변경시키고 싶은 유혹을 불러일으키는 두 가지 경우가 있습니다.

     

    1. 이 prop는 초기 값을 전달하는데만 사용되며 하위 컴포넌트는 이후에 이를 로컬 데이터 속성으로 사용하기만 합니다.

    2. prop는 변경되어야 할 원시 값으로 전달됩니다.

     

    이러한 사용 사례에 대한 적절한 대답은 다음과 같습니다.

     

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

    props: ['initialCounter'],
    data: function () {
      return { counter: this.initialCounter }
    }

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

    props: ['size'],
    computed: {
      normalizedSize: function () {
        return this.size.trim().toLowerCase()
      }
    }

     

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

     

    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
          }
        }
      }
    })

    type은 다음 네이티브 생성자 중 하나를 사용할 수 있습니다.

     

    • String

    • Number

    • Boolean

    • Function

    • Object

    • Array

    • Symbol

     

    또한, type은 커스텀 생성자 함수가 될 수 있고, assertion은 instanceof 체크로 만들어질 것입니다.

     

    props 검증이 실패하면 Vue는 콘솔에서 경고를 출력합니다(개발 빌드를 사용하는 경우). props는 컴포넌트 인스턴스가 '생성되기 전'에 검증되기 때문에 default 또는 validator 함수 내에서 data, computed 또는 methods와 같은 인스턴스 속성을 사용할 수 없습니다.

     

     

    Props가 아닌 속성

    Props가 아닌 속성은 컴포넌트로 전달되지만 해당 props는 정의되지 않은 속성입니다.

     

    명시적으로 정의된 props는 하위 컴포넌트에 정보를 전달하는데 적절하지만 컴포넌트 라이브러리를 만드는 경우 컴포넌트가 사용될 수 있는 상황을 항상 예측할 수는 없습니다. 이것이 컴포넌트가 컴포넌트의 루트 요소에 추가되는 임의의 속성을 허용해야 하는 이유입니다.

     

    예를 들어, 우리가 input에 data-3d-date-picker 속성을 요구하는 부트스트랩 플러그인으로 써드 파티 bs-date-input 컴포넌트를 사용하고 있다고 상상해보세요. 이 속성을 컴포넌트 인스턴스에 추가할 수 있습니다.

    <bs-date-input data-3d-date-picker="true"></bs-date-input>

    그리고 data-3d-date-picker="true" 속성은 bs-date-input의 루트 엘리먼트에 자동으로 추가될 것입니다.

     

    존재하는 속성 교체/병합

    아래 코드는 bs-date-input의 template이라고 가정합니다 :

    <input type="date" class="form-control">

    데이트 피커 플러그인의 테마를 추가하려면 다음과 같이 특정 클래스를 추가해야 할 수도 있습니다.

    <bs-date-input data-3d-date-picker="true" class="date-picker-theme-dark"></bs-date-input>

    이 경우 class에 대한 두 개의 서로 다른 값이 정의됩니다.

     

    • 템플릿의 컴포넌트에 의해 설정된 form-control

    • date-picker-theme-dark는 부모에 의해 컴포넌트로 전달됩니다.

     

    대부분의 속성의 경우 컴포넌트에 제공된 값은 컴포넌트에서 설정된 값을 대체합니다. 예를 들어, type="large"가 전달되면 type="date"를 대체할 것이고 아마도 망가뜨릴 것입니다! 다행스럽게도 class와 style 속성은 똑똑하기 때문에 두 값이 합쳐져서 최종 값인 form-control date-picker-theme-dark를 만듭니다.

     

    우리는 부모가 prop을 사용하여 자식에게 데이터를 전달할 수 있다는 것을 알았지만, 문제가 발생했을 때 어떻게 부모에게 다시 알릴까요? 바로 Vue의 사용자 정의 이벤트 시스템이 들어오는 곳입니다.

     

     

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

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

     

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

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

     

    Vue의 이벤트 시스템은 브라우저의 [EventTarget API]와 별개입니다. 비슷하게 작동하지만 `$on` 과 `$emit` 는 `addEventListener` 와 `dispatchEvent`의 별칭이 아닙니다.

     

    또한, 부모 컴포넌트는 자식 컴포넌트가 사용되는 템플릿에서 직접 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>
    
    <script>
      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>

    이 예제에서는 하위 컴포넌트가 외부에서 발생하는 것과 완전히 분리된다는 점에 유의해야 합니다. 부모 컴포넌트가 신경 쓸 수 있는 경우를 대비하여 자체 활동에 대한 정보를 보고 하는 것뿐입니다.

     

    컴포넌트에 네이티브 이벤트 바인딩

    컴포넌트의 루트 엘리먼트에서 네이티브 이벤트를 수신하려는 경우가 있을 수 있습니다. 이러한 경우 v-on에 .native 수식자를 사용할 수 있습니다. 예 :

    <my-component v-on:click.native="doTheThing"></my-component>

     

    .sync 수식어

    2.3.0+

     

    일부 경우에 속성에 “양방향 바인딩”이 필요할 수 있습니다. Vue 1 버전에 있던 .sync 수식어와 동일합니다. 자식 컴포넌트가 .sync를 가지는 속성을 변경하면 값의 변경이 부모에 반영됩니다. 편리하지만 단방향 데이터 흐름이 아니기 때문에 장기적으로 유지보수에 문제가 생깁니다. 자식 속성을 변경하는 코드는 부모의 상태에 영향을 미칩니다.

     

    이 때문에 .sync는 2.0 버전에서 삭제되었습니다. 그러나 재사용 가능한 컴포넌트를 만들 때 유용할 수 있다는 점을 알게 되었습니다. 부모 상태에 영향을 미치는 코드를 더욱 일관적이고 명백하게 만들어야 합니다.

     

    2.3 버전에서 속성을 위한 .sync 수식어를 다시 만들었습니다. 자동으로 v-on로 확장되는 신택스 슈가입니다.

     

    따라서 아래 코드는

    <comp :foo.sync="bar"></comp>

    아래와 같습니다.

    <comp :foo="bar" @update:foo="val => bar = val"></comp>

    하위 컴포넌트가 foo를 갱신하려면 속성을 변경하는 대신 명시적으로 이벤트를 보내야 합니다.

    this.$emit('update:foo', newValue)

     

    사용자 정의 이벤트를 사용하여 폼 입력 컴포넌트 만들기

    사용자 정의 이벤트는 v-model에서 작동하는 사용자 정의 입력을 만드는 데에도 사용할 수 있습니다. 기억하세요.

    <input v-model="something">

    위 문장은 아래와 같습니다.

    <input v-bind:value="something" v-on:input="something = $event.target.value">

    컴포넌트와 함께 사용하면 다음과 같이 간단해집니다.

    <custom-input :value="something" @input="value => { something = value }"></custom-input>

    따라서 v-model을 사용하는 컴포넌트는 (2.2.0 버전 이상에서 설정을 조작할 수 있습니다.)

     

    • value prop를 가집니다.

    • 새로운 값으로 input 이벤트를 내보냅니다.

     

    매우 간단한 통화 입력을 사용하는 모습을 보겠습니다.

    <currency-input v-model="price"></currency-input>
    
    <script>
      Vue.component('currency-input', {
        template: '\
          <span>\
            $\
            <input\
              ref="input"\
              v-bind:value="value"\
              v-on:input="updateValue($event.target.value)">\
          </span>\
        ',
        props: ['value'],
        methods: {
          // 값을 직접 업데이트하는 대신 이 메소드를 사용하여
          // 입력 값에 대한 서식을 지정하고 배치 할 수 있습니다
          updateValue: function (value) {
            var formattedValue = value
              // 공백을 제거합니다.
              .trim()
              // 소수 자릿수 2자리로 줄입니다
              .slice(
                0,
                value.indexOf('.') === -1
                  ? value.length
                  : value.indexOf('.') + 3
              )
            // 값이 아직 정규화 되지 않은 경우
            // 이를 수동으로 재정의하여 조건을 충족시킵니다.
            if (formattedValue !== value) {
              this.$refs.input.value = formattedValue
            }
            // 입력 이벤트를 통해 숫자 값을 내보냅니다.
            this.$emit('input', Number(formattedValue))
          }
        }
      })
    </srcipt>

     

    컴포넌트의 v-model 사용자 정의

    2.2.0 버전에서 추가됨

     

    기본적으로 컴포넌트의 v-model은 value를 보조 변수로 사용하고 input을 이벤트로 사용하지만 체크 박스와 라디오 버튼과 같은 일부 입력 타입은 다른 목적으로 value 속성을 사용할 수 있습니다. model 옵션을 사용하면 다음 경우에 충돌을 피할 수 있습니다:

    <my-checkbox v-model="foo" value="some value"></my-checkbox>
    
    <script>
      Vue.component('my-checkbox', {
        model: {
          prop: 'checked',
          event: 'change'
        },
        props: {
          // 다른 목적을 위해 `value` prop를 사용할 수 있습니다.
          checked: Boolean,  // `checked` prop를 명시적으로 선언해야 합니다.
          value: String
        },
        // ...
      })
    </script>

    아래와 같습니다

    <my-checkbox :checked="foo" @change="val => { foo = val }" value="some value"></my-checkbox>

     

    비 부모-자식 간 통신

    때로는 두 컴포넌트가 서로 통신할 필요가 있지만 서로 부모/자식이 아닐 수도 있습니다. 간단한 시나리오에서는 비어있는 Vue 인스턴스를 중앙 이벤트 bus로 사용할 수 있습니다.

    var bus = new Vue()
    
    // 컴포넌트 A의 메소드
    bus.$emit('id-selected', 1)
    
    // 컴포넌트 B의 created 훅
    bus.$on('id-selected', function (id) {
      // ...
    })

    보다 복잡한 경우에는 전용 상태 관리 패턴을 고려해야 합니다

    반응형

    댓글

Luster Sun