08 컴포넌트 심화 학습

vencott·2022년 2월 18일
0
post-custom-banner
💡 Vue의 가장 큰 장점 중 하나는 컴포넌트 재활용이다

8.1 컴포넌트 안에 다른 컴포넌트 사용하기

8.1.1 부모 컴포넌트와 자식 컴포넌트

src/components 폴더에 PageTitle.vue 파일을 추가한다

/src/components/PageTitle.vue

<template>
  <h2>Page Title</h2>
</template>

src/views폴더에 NestedComponent.vue 파일을 추가한다

/src/views/NestedComponent.vue

<template>
  <div>
    <PageTitle />
  </div>
</template>

<script>
import PageTitle from "../components/PageTitle.vue";
export default {
  components: { PageTitle },
};
</script>
  • 컴포넌트에서 다른 컴포넌트를 사용하려면 import 후 components에 등록한다

PageTitle.vue 컴포넌트는 다른 컴포넌트에서도 헤더로 사용할 수 있는 재사용성을 지닌다

이처럼 컴포넌트는 페이지 하나 전체가 될 수도 있고, 작은 단위 요소일 수도 있다

컴포넌트 설계는 전체 애플리케이션 개발에서 매우 중요하다

8.1.2 부모 컴포넌트에서 자식 컴포넌트로 데이터 전달하기 : Props

/src/components/PageTitle.vue

<template>
  <h2>{{ title }}</h2>
</template>

<script>
export default {
  props: {
    title: {
      type: String,
      default: "페이지 제목입니다.",
    },
  },
};
</script>
  • props에는 부모 컴포넌트로부터 전달받은 데이터가 저장된다

/src/views/NestedComponent.vue

<template>
  <div>
    <PageTitle title="부모 컴포넌트에서 자식 컴포넌트로 데이터 전달" />
  </div>
</template>

<script>
import PageTitle from "../components/PageTitle.vue";
export default {
  components: { PageTitle },
};
</script>
  • 부모 컴포넌트에선 속성을 정의하고, 정의된 속성과 동일한 이름의 속성명을 자식 컴포넌트의 props에 정의해서 사용한다

8.1.2.1 정적/동적 Props 전달

정적 데이터는 위와 같이 전달하면 되고, 동적 데이터는 v-bind를 사용해서 값을 전달한다

<page-title v-bind:title="title" />
<page-title :title="title" />

data() {
  return {
    title: "동적 페이지 타이틀"
  }
}

8.1.2.2 숫자형(Number) 전달

숫자 값을 prop으로 전달하기 위해선 v-bind를 통해서만 가능하다

<blog-post likes="42" />
  • v-bind를 사용하지 않으면 숫자 42가 아닌 문자 “42”가 된다
<!-- 정적 -->
<blog-post :likes="42" />
<!-- 동적 -->
<blog-post :likes="post.likes" />
  • v-bind를 사용할 경우 전달되는 데이터가 JS 표현식이 된다

8.1.2.3 논리 자료형(Boolean) 전달

논리 자료형 역시 v-bind로 전달한다

<!-- 정적 -->
<blog-post :is-published="true" />
<!-- 동적 -->
<blog-post :is-published="isShow" />

8.1.2.4 배열(Array) 전달

배열도 v-bind를 이용한다

<!-- 정적 -->
<blog-post :comment-ids="[1, 2, 3]" />
<!-- 동적 -->
<blog-post :comment-ids="post.commentIds" />

8.1.2.5 객체(Object) 전달

객체도 v-bind를 이용한다

<!-- 정적 -->
<blog-post :author="{name:'Veronica', age:13}" />
<!-- 동적 -->
<blog-post :author="post.author" />

8.1.2.6 객체의 속성 전달

객체 및 속성 역시 v-bind를 이용한다

<!-- 동일한 두 코드 -->
<blog-post v-bind="post" />
<blog-post id:="post.id" title:="post.title"/>

data() {
  return {
    post: {id:1, title:'공지사항'}
  }
},

8.1.2.7 prop 유효성 검사

자식 컴포넌트에서 props 옵션을 정할 때, 다음과 같은 방법을 통해 데이터에 대한 요구사항을 지정할 수 있다

  • 데이터 타입
  • 기본 값(default)
  • 필수 여부(required)
  • 유효성 검사 함수(validator)
props: {
    // Number 타입 체크
    propA: Number,
    // 여러 타입 허용
    propB: [String, Number],
    // 문자형이고 부모 컴포넌트로부터 반드시 데이터가 전달되어야 함
    propC: {
      type: String,
      required: true,
    },
    // 기본 값(100)을 갖는 숫자형
    propD: {
      type: Number,
      default: 100,
    },
    // 기본 값을 갖는 객체 타입
    propE: {
      type: Object,
      // 객체나 배열의 기본 값은 항상 팩토리 함수로부터 반환
      default: function () {
        return { message: "hello" };
      },
    },
    // 커스텀 유효성 함수
    propF: {
      validator: function (value) {
        // 값이 꼭 아래 세 문자열 중 하나와 일치
        return ["success", "warning", "danger"].indexOf(value) !== -1;
      },
    },
    // 기본 값을 갖는 함수
    propG: {
      type: Function,
      // 팩토리 함수가 아닌 기본 값으로 사용되는 함수 그 자체
      default: function () {
        return "Default function";
      },
    },
  },

8.1.3 부모 컴포넌트에서 자식 컴포넌트의 이벤트 직접 발생시키기

/src/views/ChildComponent.vue

<template>
  <div>
    <button type="button" @click="childFunc" ref="btn">click</button>
  </div>
</template>

<script>
export default {
  methods: {
    childFunc() {
      console.log("부모 컴포넌트에서 직접 발생시킨 이벤트");
    },
  },
};
</script>
  • ref="btn" : 자식 컴포넌트의 버튼 객체에 ref를 달아준다
    • HTML 태그에 ref="btn" 를 지정하면 Vue 컴포넌트의 함수에서 this.$ref 를 통해 접근이 가능하다
    • ref는 유일한 키 값을 사용해야 한다

/src/views/ParentComponent.vue

<template>
  <div>
    <button type="button" @click="childFunc" ref="btn">click</button>
  </div>
</template>

<script>
export default {
  methods: {
    callFromParent() {
      console.log("부모 컴포넌트에서 직접 호출한 함수");
    },
  },
};
</script>
  • <child-component ref="child_component" /> : 마찬가지로 child-component에 child_component라는 ref를 달아준다
  • this.$refs.child_component.$refs.btn.click(); : 자식의 버튼 객체에 접근해 click() 이벤트를 발생한다

8.1.4 부모 컴포넌트에서 자식 컴포넌트의 함수 직접 호출하기

/src/views/ChildComponent2.vue

<template>
  <div></div>
</template>

<script>
export default {
  methods: {
    callFromParent() {
      console.log("부모 컴포넌트에서 직접 호출한 함수");
    },
  },
};
</script>

/src/views/ParentComponent2.vue

<template>
  <div>
    <child-component @send-message="sendMessage" ref="child_component" />
  </div>
</template>

<script>
import ChildComponent from "./ChildComponent2.vue";
export default {
  components: { ChildComponent },
  mounted() {
    this.$refs.child_component.callFromParent();
  },
};
</script>
  • this.$refs.child_component.callFromParent(); : 부모 컴포넌트에서 자식 컴포넌트를 $refs를 사용하여 접근하고, 직접 함수를 호출한다

8.1.5 부모 컴포넌트에서 자식 컴포넌트의 데이터 옵션 값 직접 변경하기

/src/views/ChildComponent3.vue

<template>
  <div>
    <h1>{{ msg }}</h1>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: "",
    };
  },
};
</script>

/src/views/ParentComponent3.vue

<template>
  <div>
    <child-component @send-message="sendMessage" ref="child_component" />
    <button type="button" @click="changeCildData">Change Child Data</button>
  </div>
</template>

<script>
import ChildComponent from "./ChildComponent3.vue";
export default {
  components: { ChildComponent },
  methods: {
    changeCildData() {
      this.$refs.child_component.msg = "부모 컴포넌트가 변경한 데이터";
    },
  },
};
</script>
  • this.$refs.child_component.msg = "부모 컴포넌트가 변경한 데이터"; : 마찬가지로 $refs 를 통해 접근한 뒤 데이터를 직접 변경한다

8.1.6 자식 컴포넌트에서 부모 컴포넌트로 이벤트/데이터 전달하기 (커스텀 이벤트)

자식 컴포넌트에서 부모 컴포넌트로 이벤트를 전달하기 위해선 $emit 을 이용한다

/src/views/ChildComponent4.vue

<template>
  <div>
    <h1>{{ msg }}</h1>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: "자식 컴포넌트로부터 보내는 메시지",
    };
  },
  mounted() {
    this.$emit("send-message", this.msg);
  },
};
</script>
  • this.$emit("send-message", this.msg); : 자식 컴포넌트가 mount 되면 부모 컴포넌트의 send-message 이벤트를 호출한다

/src/views/ParentComponent4.vue

<template>
  <div>
    <child-component @send-message="sendMessage" ref="child_component" />
  </div>
</template>

<script>
import ChildComponent from "./ChildComponent3.vue";
export default {
  components: { ChildComponent },
  methods: {
    sendMessage(data) {
      console.log(data);
    },
  },
};
</script>
  • <child-component @send-message="sendMessage" /> : 커스텀 이벤트 send-message는 자식 컴포넌트에서 $emit 으로 호출한다

8.1.7 부모 컴포넌트에서 자식 컴포넌트의 데이터 옵션 값 동기화하기

부모 컴포넌트에서 computed를 이용하면 자식 컴포넌트에 정의된 데이터 옵션값의 변경사항을 항상 동기화 할 수 있다

/src/views/ChildComponent5.vue

<template>
  <div>
    <button type="button" @click="childFunc" ref="button">
      자식 컴포넌트 데이터 변경
    </button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: "메시지",
    };
  },
  methods: {
    childFunc() {
      this.msg = "변경된 메시지";
    },
  },
};
</script>

/src/views/ParentComponent5.vue

<template>
  <div>
    <button type="button" @click="checkChild">자식 컴포넌트 데이터 조회</button>
    <child-component ref="child_component" />
  </div>
</template>

<script>
import ChildComponent from "./ChildComponent5.vue";
export default {
  components: { ChildComponent },
  computed: {
    msg() {
      return this.$refs.child_component.msg;
    },
  },
  methods: {
    checkChild() {
      alert(this.msg);
    },
  },
};
</script>
  • 부모 컴포넌트에선 computed 옵션을 사용해 자식 컴포넌트의 msg 값을 감지한다
  • computed 옵션을 사용하면 자식 컴포넌트의 데이터가 변경될 때 마다 $emit 을 하지 않아도 항상 최신 상태를 유지할 수 있다는 장점이 있다

8.2 Slot

컴포넌트는 재활용 가능하며, 여러 컴포넌트를 import해서 사용할 수 있다고 배웠다

실무에선 굉장히 비슷한 UI와 기능을 가지고 있는데, 아주 일부만 달라 재활용을 하지 못하는 경우가 많다

slot은 컴포넌트 내에서 다른 컴포넌트를 사용할 때 쓰는 컴포넌트의 마크업을 재정의하거나 확장하는 기능으로, 재활용성을 높여준다

예를 들면, 팝업(Modal)은 굉장히 많은 화면에서 사용한다

팝업의 기본 틀에 해당하는 컴포넌트를 slot을 이용해 만들고, 개발자는 컨텐츠에 해당하는 부분만 작성한다

src/views/SlotModalLayout.vue

<template>
  <div class="modal-container">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>
  • name을 지정해서 사용하는 slot을 Named Slots 이라고 한다
  • 이 컴포넌트는 정해진 html 구조를 갖게 되고, 개발자는 각 slot에 해당하는 코드만 작성하면 되므로 동일한 디자인의 팝업을 만들 수 있다

src/views/SlotUseModalLayout.vue

<modal-layout>
    <template v-slot:header>
        <h1>팝업 타이틀</h1>
    </template>
    <template v-slot:default>
        <p>팝업 컨텐츠 1</p>
        <p>팝업 컨텐츠 2</p>
    </template>
    <template v-slot:footer>
        <button>닫기</button>
    </template>
</modal-layout>
  • Slot을 사용하는 컴포넌트에서는 삽입한 컴포넌트 안에서 template 태그를 사용해서 html 코드를 작성한다
  • v-slot:[slot 이름] 디렉티브를 사용해서 동일한 이름의 slot 위치로 html 코드가 삽입된다
  • name이 없는 slot은 v-slot:deafult로 지정한다

slot 사용 여부에 따라 코드는 아래와 같이 차이가 난다

slot 미사용

<template>
  <div>{{ title }}</div>
</template>

<script>
export default {
  props: {
    title: {
      type: String,
      default: "페이지 제목입니다.",
    },
  },
};
</script>

------------------------------------------------

<PageTitle v-bind:title="slot 미사용"><PageTitle>

slot 사용

<template>
  <slot></slot>
</template>

------------------------------------------------

<PageTitle>slot 사용<PageTitle>
  • 단순히 페이지 타이틀을 만들기 위해서 props를 정의할 필요도 업고, 부모에서 자식으로 props 데이터를 전달할 필요도 없어진다
  • 코드도 훨씬 간결하고 직관적으로 변경되었다

8.3 Provide/Inject

부모에서 자식으로 데이터를 내려줄 때엔 props를 사용한다

그런데 만약 컴포넌트 계층 구조가 복잡해 부모에서 자식으로, 자식에서 그 자식으로 데이터를 전달해야하는 경우가 생긴다면, 코드가 굉장히 복잡해질 것이다

이러한 경우에 부모 컴포넌트에선 provide 옵션을, 자식 컴포넌트에선 inject 옵션을 통해 데이터를 쉽게 전달할 수 있다

예를 들어, 컴포넌트 구조가 Parent > Child > ChildChild 일 때,

Parent에서 ChildChild로 데이터를 전달하려면 Parent → Child → ChildChild 의 3단계를 거쳐 전달해야 하지만, Provide/Inject를 사용하면 한번에 바로 전달할 수 있다

src/views/ProvideInject.vue

<template>
  <provide-inject-child />
</template>

<script>
import ProvideInjectChild from "./ProvideInjectChild.vue";
export default {
  components: { ProvideInjectChild },
  data() {
    return {
      items: ["A", "B"],
    };
  },
  provide() {
    return {
      itemLength: this.items.length,
    };
  },
};
</script>
  • 자식 컴포넌트로 전달하고자 하는 데이터를 provide에 정의한다

src/views/ProvideInjectChild.vue

<script>
export default {
  inject: ["itemLength"],
  mounted() {
    console.log(this.itemLength);
  },
};
</script>
  • inject: ["itemLength"] : 부모 컴포넌트로부터 전달받고자 하는 데이터와 동일한 속성 이름으로 inject에 문자열 배열로 정의한다
  • inject를 통해서 데이터를 전달받는 자식 입장에선, 어떤 부모로부터 온 데이터인지 확인이 안된다는 단점이 있다

8.4 Template refs

Vue 개발시 특별한 경우가 아니면 HTML 객체에 바로 접근해서 코드를 구현할 일은 없다

하지만 어쩔수 없이 JS에서 HTML 객체에 바로 접근 해야한다면 HTML 태그에 id 대신 ref를 사용한다

<input type="text" ref="title" />

this.$refs를 이용해 ref 속성에 지정된 이름으로 HTML 객체에 접근이 가능하다

this.$refs.title.focus();

출처: 고승원 저, 『Vue.js 프로젝트 투입 일주일 전』, 비제이퍼블릭(2021)


profile
Backend Developer
post-custom-banner

0개의 댓글