Skip to content

Latest commit

 

History

History
433 lines (391 loc) · 11.8 KB

File metadata and controls

433 lines (391 loc) · 11.8 KB

Vue 컴포넌트간 통신

중첩된 컴포넌트간 통신

중첩된 컴포넌트들은 개별 컴포넌트가 독립된 공간을 갖고 Javascript의 스코프 체이닝과 달리 하위 컴포넌트가 상위 컴포넌트의 변수를 참조할 수 없다.

  • 부모, 자식 컴포넌트간 필연적으로 의사소통이 필요한데 부모는 자식에게 데이터를 전달 할 수 있으며, 자식은 부모에게 이벤트를 전달 할 수 있다.
  • 즉, 중첩된 컴포넌트 사이 통신 방법을 명시적으로 처리할 수 있는 인터페이스를 제공
    • 부모 컴포넌트의 템플릿에서는 자식 컴포넌트를 사용할 수 있다.
    • 부모 컴포넌트의 템플릿에서 속성 정의를 통해 자식 컴포넌트에게 데이터를 전달 할 수 있다.
    • 또한 커스텀 이벤트 리스너를 할당해서 자식 컴포넌트에서 방출(emit)시킨 이벤트를 받을 수 있다.

Props / Props 검증

  • Props로 데이터 전달하기

부모 컴포넌트는 자식 컴포넌트에 속성(props)을 사용하여 하위 컴포넌트에 전달할수 있다.

<!-- [ParentComp.vue 파일]  -->
<template lang="pug">
div.component
  h1 {{ mine }}
  child(
    class="child-component"
    parent-name="ParentComponent"
    :temp="1990"
    :prop-message="message"
  )
</template>
<script>
import ChildComp from './ChildComp';
export default {
  components: {
    'child': ChildComp
  },
  data () {
    return {
      
      message: ['오늘도', '내일도', '행복하길'],
      mine: 'Parent Component'
    }
  }
}
</script>
<!-- [ChildComp.vue 파일] -->
<template lang="pug">
div.component
  h1 {{ mine }}
  h3 상위 컴포넌트로 부터 전달 받은 속성들
  ul
    li {{ parentName }}
    li {{ temp }}
    li Computed: {{ mine_propMessage }}
  
</template>

<script>
export default {
  props: {
    parentName: {
      type: String,
      validator(v){
        return v.trim() !== '';
      }
    },
    temp: {
      type: Number,
      required: true,
    },
    propMessage: {
      type: Array,
      // required: false,
      // 주의! 참조형 데이터의 경우, 함수를 통해 객체를 반환해야 한다.
      default: function(){
        return ['오늘도'];
      }
    }
  },
  data () {
    return {
      mine: 'Child Component',
      mine_prop_msg: this.propMessage,
      copyed_message: []
    }
  },
  computed: {
    mine_propMessage() {
      this.copyed_message = this.propMessage;
      return this.copyed_message.join(' ');
    }
  }
}
</script>

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

  • 일반속성 바인딩 & 동적 속성 바인딩

    위 예제 [ParentComp.vue 파일]에서 temp 의 경우, 숫자로 전달시키기 위해서는 temp="1990"는 문자열 "1990"으로 인식하므로 이를 숫자로 전달하기 위해서는 동적 속성바인딩을 이용하여, :temp="1990"와 같이 숫자로 전달시킨다.

  • 단방향 데이터 흐름 모든 Props속성는 하위 속성과 상위 속성 사이의 단방향 바인딩을형성. => 하위 컴포넌트에서 Props를 변경할 경우 console창 Error !

    : 하위 컴포넌트의 Props를 변경하고자 할 경우

    1. 자식컴포넌트의 Props를 data(로컬데이터 속성)으로 할당받아 사용.
    2. Propsr값에 의존하는 computed 속성으로 정의하여 사용.
    3. 배열 혹은 객체 (참조형 데이터)의 경우 값을 복사하여 사용.
  • Props 검증

사용자로 부터 Props 에 대한 요구 사항을 지정한다. => [ChildComp.vue 파일]의 경우, parentName,temp,propMessage 데이터 타입(type), 사용자 정의 검증 (validator), 기본값(default), 반드시 전달되어야하는값 (required) 등을 설정할 수 있다.

export default {
  props: {
    parentName: {
      type: String,
      validator(v){
        return v.trim() !== '';
      }
    },
    temp: {
      type: Number,
      required: true,
    },
    propMessage: {
      type: Array,
      // required: false,
      // 주의! 참조형 데이터의 경우, 함수를 통해 객체를 반환해야 한다.
      default: function(){
        return ['오늘도'];
      }
    }
  }

Custom Events

:자식이 $emit을 방출하는 순간. 커스텀이벤트를 방출하여 이를 감지한 부모가 이벤트 적용한다. :부모 컴포넌트는 자식 컴포넌트가 사용되는 템플릿에서 직접 v-on 을 사용하여 자식 컴포넌트에서 보내진 이벤트를 들을 수 있다.

  • $on(이벤트 감지) & $emit(이벤트 트리거)
<!-- [Counter.vue 파일 내 methods] -->

methods: {
  dispatchCount(){
      this.$emit('changeCount',this.index,this.count);
    }
}    
<!-- [TotalCounter.vue 파일] -->

<template lang="pug">
  div.total-counter
    div.total-count {{ total_count }}
    counter(
      v-for="(counter,index) in mine_counters"
      v-on:changeCount="recieveCount"
    )
</template>

<script>
import Counter from './Counter';
export default {
  components: { Counter },
  props: {
    counters: {
      type: Array,
      required: true
    },
    appMood: String
  },
  computed:{
    total_count(){
        return this.mine_counters.reduce((prev,next)=>prev+next);
        
    }
  },
  
  data () {
    return {
      mine_counters: this.counters.slice()
    }
  },
  methods: {
    
    recieveCount(index,count){
      this.mine_counters.splice(index,1,count);
    }
  }
}

=> [Counter.vue 파일 내 methods] 내에 정의된 dispatchCount메소드를 changeCount로 $emit 하여 [TotalCounter.vue 파일 ]내 template 코드에서 v-on:changeCount="recieveCount" 이를 감지하여 사용.


글로벌 이벤트 버스(Global Event Bus)

부모 / 자식 관계가 아닌 컴포넌트간 통신을 통해 상태를 관리하기 위한 가장 손쉽고 견고한 방법은 상태를 관리하는 객체를 사용하는 것이다. 이벤트 버스는 이벤트가 이동하는 통로와 비슷한 개념이다.

<!-- App.vue -->
  <template lang="pug">
    #app
      otherparent
        p
      parent
        child
  </template>

  <script>
    import otherparent from './component/otherparent'
    import parent from './component/parent'
    import child from './component/child'

    export default {
      name: 'app',
      components: {
        otherparent,
        parent,
        child
      }
    }
  </script>
<!-- parent.vue -->
  <template lang="pug">
    .parent(@click="callChild")
      slot
      | 부모
  </template>

  <script>
  import Vue from 'vue';
  import EventBus from '../EventBus'; // 이벤트버스를 불러온다.
  export default {
    methods:{
      callChild(){
        EventBus.$emit('call-child', 40); // 이벤트를 받아온다.
      }
    },
    data () {
      return {
        
      }
    }
  }
  </script>
<!-- child.vue -->
  <template lang="pug">
    //- 부모이벤트 캡쳐링이 일어나지 않도록 하는 방법.
    .child(@click.stop="callChild")
      slot
      | 자식
  </template>
  <script>
  import Vue from 'vue';
  import EventBus from '../EventBus';
  export default {
    // otherparent.vue 에서 보낸 이벤트를 받는다.
    methods:{
      callChild(){
        EventBus.$emit('call-child', 10);
        // 이벤트버스를 받는다.
      }
    }
  }
  </script>
<!-- otherparent.vue -->
  <template lang="pug">
    .parent.is-other {{ payload }}
      | 다른 부모야
      slot
  </template>

  <script>
  import Vue from 'vue';
  import EventBus from '../EventBus';

  export default {
    // 이곳에서 직접 EventBus를 건다.
    mounted(){
      EventBus.$on('call-child', (payload)=>{ // on 이벤트를 이벤트버스에 건다.
        this.payload = payload;
      });
    },
    data(){
      return {
        payload: 0
      }
    }
  }
  </script>

slot

  • template 과 같은 웹 컴포넌트 기술로 웹 컴포넌트 내에 있는 자리 표시자로 사용자가 별도의 DOM 트리를 만들고 함께 제시 할 수 있는 자체 마크업으로 채울 수 있게 하는 역할을 한다.
  • slot 요소는 외부에서 전달된 HTML 코드가 삽입되는 투입구라고 할 수 있다.
  • 하위 컴포넌트 템플릿에 최소한 하나의 slot 콘텐츠가 포함되어 있지 않으면 부모 콘텐츠가 삭제 되고 속성이 없는 슬롯이 하나뿐일 경우 전체 내용조각이 DOM의 해당 위치에 삽입되어 슬롯 자체를 대체한다.
  • 특히 부모 컴포넌트가 항상 같지 않을 때 컴포넌트 구성에 있어서 상당히 유용하다
<!-- FormHelper.vue -->
<form class="form">
  <h3>{{ title }}</h3>
  <slot name="help"></slot>
  <slot name="elements"></slot><!-- 자식요소가 들어온다 -->
  <slot name="buttons"></slot>
</form>
<!-- App.vue -->
<form-helper title="Request Form">
  <div slot="help">
    매니저에게 요청하세요.
  </div>
  <div slot="elements">
    <input type="text" name="item_name" value="">
    <input type="text" name="item_value" value="">
  </div>
  <div slot="buttons">
    <button type="button" name="button">Request</button>
  </div>
</form-helper>

named slot

  • slot 엘리먼트는 특별한 속성인 name을 가지고 있다 이 속성은 어떻게 내용을 배포해야 하는지를 커스터 마이징 하는데 사용한다.
<!--아래 컨텐츠에 page 컴포넌트를 삽입-->
<aside>
 <slot name="sidebar"></slot>
</aside>
<main>
 <slot name="content"></slot>
</main>
<!--page 컴포넌트 -->
<!--named slot으로 컨텐츠가 어디에 들어갈지 결정한다 -->
<page>
   <p slot="sidebar">This is sidebar content.</p>
   <article slot="content"></article>
</page>
<!--결과 -->
<aside>
  <p>This is sidebar content.</p>
</aside>
<main>
  <article></article>
</main>

scoped slot

  • Scoped slot은 Vue.js 2.1.0 버전 부터 만들어진 새 기능으로 자식요소의 속성을 전달하고 부모로부터 접근할 수 있다.
  • 스코프 슬롯은 이미 렌더링된 엘리먼트 대신 재사용 가능한 템플릿으로 작동하는 특별한 유형의 slot이다
  • 하위 컴포넌트에서 속성을 컴포넌트에게 전달하는 것처럼 slot에게 데이터를 전달하기만 하면 된다.
  • Scope 값은 자식으로부터 전달 된 props 객체를 담고 있는 임시 변수의 이름
// 자식요소에서 slot을 이용하여 데이터를 전달
const Child = {
  data () {
    return { msg: 'hello from child' }
  },
  template: `
    <div class="child">
      <slot :text="msg"></slot>
    </div>
  `
}
// 부모 요소 
const Parent = {
  components: { Child },
  template: `
    <div class="parent">
      <child>
        <template scope="props">
          <span>hello from parent</span>
          <span>{{ props.text }}</span>
        </template>
      </child>
    </div>
  `
}
//<!--렌더링 결과 -->
<div class="parent">
  <div class="child">
    <span>hello from parent</span>
    <span>hello from child</span>
  </div>
</div>

배열과 객체 복사

  • 자바스크립트에서는 배열 및 객체를 다른 변수에 할당하면 참조값이 할당된다. 하지만 종종 다른 작업을 위해 참조 없는 복사가 필요해 질때가 있다
  • 객체 복제 하기
// Object.assign() 메소드 이용 
var obj = {a: 1};
var clone = Object.assign({}, obj);
console.log(clone) //--> {a: 1};
// 메서드 사용
var cloneObj =  function(obj){
  return JSON.parse(JSON.stringify(obj));
};
var obj = {a: 1, b: 3};
// test 
console.log(obj) // {a: 1, b: 3}
cloneObj(obj) // {a: 1, b: 3}
  • Array 복사
var arr = [1, 2 ,4];
var clone = arr.slice();
console.log(clone); // [1, 2, 4];