지식보부상님의 공부 일지

Vue [08] 컴포넌트 심화 본문

KB IT's Your Life/Vue

Vue [08] 컴포넌트 심화

지식보부상님 2025. 3. 27. 11:37

[1] 단일 파일 컴포넌트에서의 스타일

◈ 전역 CSS

- src/main.js 에서 임포트한 CSS 스타일

- 페이지 전체에 적용됨

- assets/main.css 에 기술

- 다른 컴포넌트들과의 스타일과 충돌 피하는 것 고려해야

 

◈ 범위 CSS

- 컴포넌트가 렌더링하는 요소에 특성 기반 추가적인 식별자 부여하여 => 충돌 회피

- <style> 태그에 <style scoped> 이용하면 각각의 태그에 대한 스타일 지정이 가능 i.e. 식별자가 부여됨

더보기
더보기
더보기

Child1.vue

<template>
  <div class="child">
    <h2>Child1</h2>
  </div>
</template>

<sciprt>
    export default{
        name: 'Child1',
    };
</sciprt>

<style scoped>
.child {
  background-color: yellow;
  border: solid 1px black;
  margin: 1.5em;
  padding: 1em;
}
</style>

 

Child2.vue

<template>
  <div class="child">
    <h2>Child2</h2>
  </div>
</template>

<sciprt>
      export default{
          name: 'Child2',
      };
  </sciprt>

<style scoped>
.child {
  background-color: skyblue;
  border: solid 1px black;
  margin: 1.5em;
  padding: 1em;
}
</style>

Child3.vue

<template>
  <div class="child">
    <h2>Child3</h2>
  </div>
</template>

<sciprt>
      export default{
          name: 'Child3',
      };
  </sciprt>

<style>
.child {
  background-color: pink;
  border: solid 1px black;
  margin: 1.5em;
  padding: 1em;
}
</style>

App.vue

<template>
  <div>
    <Child1 />
    <Child2 />
    <Child3 />
  </div>
</template>

<script>
import Child1 from './components/Child1.vue';
import Child2 from './components/Child2.vue';
import Child3 from './components/Child3.vue';

export default {
  name: 'App',
  components: { Child1, Child2, Child3 },
};
</script>

◈ CSS 모듈

- <style module> 이용하여 CSS 스타일을 객체처럼 처리

 

[2] 슬롯

◈ 슬롯(Slot)

- 부모~자식 컴포넌트 사이에 템플릿 정보를 전달하는 방법

cf) 부모~자식 컴포넌트 사이에 정보 교환 방법: props, 이벤트 (Vue [07] 참고)

- 부모 컴포넌트는 템플릿을 결정, 자식 컴포넌트는 위치를 결정하여 부모→자식으로 템플릿 전달

- 자식 컴포넌트 원하는 위치에 <slot></slot>

- 전달되는 템플릿 없는 경우 보여줄 fallback UI는 <slot>여기!</slot> 사이에 작성

 

 명명된 슬롯(Named Slot)

- 자식 컴포넌트에 slot 이 여러개 있으면 구분이 필요 => slot name 이용

- 자식 컴포넌트: <slot name="슬롯명"></slot>

- 부모 컴포넌트: <... v-slot: 슬롯명></....>   *슬롯 명에 " "(큰따옴표) 없음 주의!

- 화면 레이아웃 관리 목적으로 주로 사용

 

◈ 범위 슬롯(Scoped Slot)

- 부모 컴포넌트에서 템플릿에 자식 컴포넌트의 데이터를 바인딩할 때 사용

- 전달된 데이터는 슬롯 템플릿 내부범위에서만 사용 가능

더보기
더보기
더보기

src/components/FancyPhotoBox.vue (자식 컴포넌트)

<template lang="">
  <div class="card">
    <slot name="title"></slot>
    <slot name="photo"></slot>
  </div>
</template>
<script>
export default {
  name: 'FancyPhotoBox',
};
</script>
<style scoped>
.card {
  padding: 20px;
  background-color: skyblue;
  width: 300px;
  height: 400px;
  border-radius: 10%;
  border-color: gray;
  border-style: solid;
  border-width: 10px;
  text-align: center;
}
</style>

 

src/components/FancyPhotoBox (부모 컴포넌트)

<template lang="">
  <div>
    <FancyPhotoBox>
      <template v-slot:title>
        <h1>침교동을 아시나요?</h1>
      </template>
      <template v-slot:photo>
        <img
          src="data:image/jpeg;base64,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"
          alt="침교동"
          width="300px"
        />
      </template>
    </FancyPhotoBox>
  </div>
</template>

<script>
import FancyPhotoBox from './FancyPhotoBox.vue';
export default {
  name: 'FancyPhotoBoxParent',
  components: { FancyPhotoBox },
};
</script>
<style lang=""></style>

 

src/App.vue

<script setup>
import FancyPhotoBoxParent from './components/FancyPhotoBoxParent.vue';
</script>

<template>
  <FancyPhotoBoxParent />
</template>

 

[3] 동적 컴포넌트

◈ 동적 컴포넌트(Dynamic Component)

- 화면 동일한 위치에 여러 컴포넌트 표현해야 하는 경우 用 (v-if 적용하는 것과 동일 효과)

- <component> 요소 템플릿에 작성

 

[4] 컴포넌트에서의 v-model 디렉티브

◈ 사용자 정의 v-model 만들기

- 부모 컴포넌트: <child-component v-model:message="parentMessage" />

- 자식 컴포넌트:  update:사용자정의 모델명

{
  name: "ChildComponent",
  props: {message:String},
  template:`
  <input type="text" :value="message"
  @input="$emit('update:message', $event.target.value)"/>
  `
}

 

[5] provide, inject를 이용한 공용 데이터 사용

◈ provide, inject

- 기존 props는 계층 구조 따라 연속적으로 속성 전달해야한다는 문제점이 있었음

=> 공용 데이터를 부모 컴포넌트(App)에서 provide 하고, 하위 컴포넌트 어디든 데이터 inject 하면 用 가능하도록 함

- 공통 조상끼리만 사용 가능하다는 단점도 있음 

 

[6] 텔레포트

◈ 텔레포트(Teleport)

- 컴포넌트 트리 계층구조와 관계없이 별도의 요소에 렌더링해야하는 경우 用

- 애플리케이션에서 모달, 튤립과 같이 메이니 화면에 독립적이게 공유 UI 제공하는 경우 用

더보기
더보기
더보기

src/components/Modal.vue (자식 컴포넌트)

<template lang="">
  <div class="modal-overlay" v-if="visible">
    <div class="modal">
      <button class="close" @click="sendClose">닫기</button>
      <div class="modal-content">
        <slot></slot>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'Modal',
  props: {
    visible: {
      type: Boolean,
      required: true,
    },
  },
  methods: {
    sendClose() {
      this.$emit('close');
    },
  },
};
</script>
<style scoped>
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
}

.modal {
  background: white;
  padding: 20px;
  border-radius: 5px;
  position: relative;
}

.close-button {
  position: absolute;
  top: 10px;
  right: 10px;
  background: transparent;
  border: none;
  font-size: 20px;
  font-weight: 700;
  cursor: pointer;
}

.modal-content {
  margin-top: 40px;
}
</style>

 

src/components/ModalParent.vue (부모 컴포넌트)

<template lang="">
  <div>
    <button @click="showModal">모달 열기</button>
    <Modal :visible="isModalVisible" @close="hideModal">
      <h2>모달 내용</h2>
      <p>안녕하세요</p>
    </Modal>
  </div>
</template>

<script>
import Modal from './Modal.vue';
export default {
  name: 'ModalParent',
  components: { Modal },
  data() {
    return {
      isModalVisible: false,
    };
  },
  methods: {
    showModal() {
      this.isModalVisible = true;
    },
    hideModal() {
      this.isModalVisible = false;
    },
  },
};
</script>
<style lang=""></style>

 

src/App.vue

<script setup>
import ModalParent from './components/ModalParent.vue';
</script>

<template>
  <ModalParent />
</template>