<Vue 3와 타입스크립트로 배우는 프런트엔드 개발> ch8 ~ ch9

2025. 3. 6.·🎨 프론트엔드 공부/Vue & Nuxt

ch8. 컴포넌트 간 연계

SFC(Single File Component)

- .vue 확장자로 이루어지는 파일로 HTML + CSS + JS 세트 (.jsx와 비슷)

 

scoped CSS 메커니즘

1) 컴포넌트별 hash 문자열이 생성되고, 해당 컴포넌트가 렌더링되면 HTML data attributes를 통해 엘리먼트에 이름이 붙여짐

예) v-eee87bea 라는 has 문자열 data attributes 생성

<section>
  <h2>v-model을 포함한 컴포넌트</h2>
  <section data-v-eee87bea="">
    <p data-v-eee87bea="">이름 없음 님이네요!</p>
    <input type="text" data-v-eee87bea="">
  </section>

  <section data-v-eee87bea="">
    <p data-v-eee87bea="">이름 없음 님이네요!</p>
    <input type="text" data-v-eee87bea="">
  </section>
</section>

 

2) CSS에서 해당 attributes에 스타일링

section[data-v-eee87bea] {
  border: orange 1px dashed;
  margin: 10px;
}

 

전역적으로 적용하고 싶은 CSS는 App.vue에 스타일링하고, 이외에는 각 개별 컴포넌트에 scoped 형식으로 작성한다

 

Provide와 Inject: props 드릴링을 방지하며 부모 -> 자식 데이터 전달

- provide(데이터 이름, 반응형 값) 형태로 부모 컴포넌트에서 선언하면

- Inject(데이터 이름) 형태로 변수에 저장하여 사용 가능

- inject로 받은 데이터를 v-model 을 통해 바꾸면, 자식 -> 부모 데이터 전송 가능 (값이 바뀜)

- provide 에서 일반 값을 전달해야 한다면, reactive 를 씌워 전달하면 된다

- inject를 통해 provide 된 데이터를 사용할 때는 타입 지정을 해줘야 함 (안해주면 기본 unknown 상태)

 

예시: app.vue 에서 provide 된 반응형 값을 자식 컴포넌트들에서 inject 하고 변경할 수 있는 양방향 데이터 바인딩

- 부모 컴포넌트 (app.vue)

<template>
  <BaseSection />
</template>

<script setup lang="ts">
import type { Member } from "~/interfaces";
import BaseSection from "~/components/BaseSection.vue";

const memberListRef = ref({
  33456: {
    id: 33456,
    name: "소희",
    email: "abc@sample.com",
    points: 35,
    note: "신규 가입 특전",
  },
  47783: {
    id: 47783,
    name: "채영",
    email: "def@sample.com",
    points: 53,
  },
});
provide("memberListRef", memberListRef as Ref<{ [key: number]: Member }>);
</script>

 

- 자식1 컴포넌트 (BaseSection.vue)

<template>
  <section>
    <h2>회원 리스트</h2>
    <p>모든 회원의 보유 포인트 합계: {{ totalPoints }}</p>
    <OneMember v-for="id in Object.keys(memberListRef)" :id="+id" :key="id" />
  </section>
</template>

<script setup lang="ts">
import type { Member } from "~/interfaces";
const memberListRef = inject<Ref<{ [key: number]: Member }>>("memberListRef", ref({}));

const totalPoints = computed(() => {
  let total = 0;
  // Map 객체의 값만 배열 형태로 순회하기 위해 values() 사용
  for (const member of Object.values(memberListRef.value)) {
    total += member.points;
  }
  return total;
});
</script>

<style scoped>
section {
  border: orange 1px dashed;
  margin: 10px;
}
</style>

 

- 자식1-1 컴포넌트 (OneMember.vue)

- v-model에 연결하여 변경하면 변경된 member가 memberListRef 에 반영됨(양방향 데이터 바인딩)

<template>
  <section>
    <h4>{{ member.name }} 님의 정보</h4>
    <dl>
      <dt>ID</dt>
      <dd>{{ id }}</dd>
      <dt>메일 주소</dt>
      <dd>{{ member.email }}</dd>
      <dt>보유 포인트</dt>
      <dd>
        <input v-model.number="member.points" type="number" />
      </dd>
      <dt>비고</dt>
      <dd>{{ localNote }}</dd>
    </dl>
  </section>
</template>

<script setup lang="ts">
import type { Member } from "~/interfaces";

interface Props {
  id: number;
}

const props = defineProps<Props>();
const memberListRef = inject<Ref<{ [key: number]: Member }>>("memberListRef", ref({}));
const member = computed(() => memberListRef.value[props.id]);
const localNote = computed(() => {
  let localNote = member.value.note;
  if (localNote === undefined) {
    localNote = "--";
  }
  return localNote;
});
</script>

 

ch9. 자식 컴포넌트 활용

slot의 fallback

- < slot > 내부에 작성한 내용은 slot의 fallback이 된다

 

예: slot 내부에 대체 컨텐츠 작성

- 이 컴포넌트를 사용할 때 내부에 어떤 내용도 전달하지 않으면 slot 내부에 작성한 내용이 렌더링된다

<template>
  <section class="box">
    <h1>{{ name }} 님의 상황</h1>
    <slot>
      <p>문제 없습니다</p>
    </slot>
  </section>
</template>

<script setup lang="ts">
interface Props {
  name: string;
}

defineProps<Props>();
</script>

<style>
.box {
  border: green 1px solid;
  margin: 10px;
}
</style>

 

명명된 slot

- 기본적으로 < slot > 을 여러개 사용할 수도 있다 -> 동일한 내용이 렌더링된다

<template>
  <section class="box">
    <h1>{{ name }} 님의 상황</h1>
    <slot />
    <slot />
  </section>
</template>

 

- slot의 attributes로 name을 사용하여 이름을 붙이면 별도의 다른 slot을 각각 렌더링할 수 있다

- 부모 컴포넌트에서는 < template > 에 v-slot 으로 slot name을 전달하면 된다

- v-slot 은 # 로 사용될 수 있다!

예) 기본값 사용: v-slot="default" (#default 와 동일) -> < slot > 에 전달됨

예) 명명된 slot 사용: v-slot="detail" (#detail) -> < slot name="detail" > 에 전달됨

 

자식 컴포넌트 OneSection.vue

<template>
  <section class="box">
    <h1>{{ name }} 님의 상황</h1>
    <slot>
      <p>문제 없습니다</p>
    </slot>
    <h4>상세 내용</h4>
    <slot name="detail">
      <p>특별히 없습니다</p>
    </slot>
  </section>
</template>

<script setup lang="ts">
interface Props {
  name: string;
}

defineProps<Props>();
</script>

<style>
.box {
  border: green 1px solid;
  margin: 10px;
}
</style>

 

부모 컴포넌트 app.vue

<template>
  <section>
    <h2>Slot 이용</h2>
    <OneSection :name="yeonghee">
      <template #default>
        <p>문제 발생</p>
      </template>
      <template #detail>
        <ul>
          <li v-for="r in 5" :key="r">{{ r }}번째 문제</li>
        </ul>
      </template>
    </OneSection>
    <OneSection :name="cheolsoo" />
  </section>
</template>

<script setup lang="ts">
const yeonghee = ref("영희");
const cheolsoo = ref("철수");
</script>

 

- v-slot 에 동적인 값을 사용하려면 v-slot:[slotName] 형태로 사용한다

 

slot의 props 사용하기

- slot 에서 선언한 props를 사용할 수 있다

 

자식 컴포넌트 OneSection.vue

<template>
  <section>
    <slot :member-info="memberInfo">
      <h1>{{ memberInfo.name }} 님의 상황</h1>
      <p>{{ memberInfo.state }}</p>
    </slot>
  </section>
</template>

<script setup lang="ts">
const memberInfo = reactive({
  name: "영희",
  state: "문제없습니다",
});
</script>

 

부모 컴포넌트 app.vue

- v-slot="{ memberInfo }" 대신 #default="{ memberInfo }" 처럼 사용해도 된다 (slot name 없으므로)

- { slotPropsName } 형태로 전달하는 것은 slotProps = { slotPropsName } 처럼 한 번 꺼낸 것으로 이해하면 된다

<template>
  <section>
    <OneSection>
      <template v-slot="{ memberInfo }">
        <dl>
          <dt>이름</dt>
          <dd>{{ memberInfo.name }}</dd>
          <dt>상황</dt>
          <dd>{{ memberInfo.state }}</dd>
        </dl>
      </template>
    </OneSection>
  </section>
</template>

 

동적 컴포넌트 활용: KeepAlive

- < component > 엘리먼트를 사용하고 v-bind:is 어트리뷰트를 사용하여 동적으로 컴포넌트를 렌더링시킬 수 있다

=> 이 때, < component > 를 < KeepAlive >  로 래핑하면, 한 번 렌더링됐던 컴포넌트의 상태가 보존된다

=> Vue 컴포넌트 라이프사이클에 따르면 activated -> deactivated 상태로 되돌아가는 것을 의미한다. deactivated 상태는 unmounted 상태와 달리 컴포넌트의 상태를 보존하며 임시로 렌더링하지 않을 뿐이다

 

예) KeepAlive 로 래핑하여 자식 컴포넌트가 동적으로 변화할 때 기존 상태를 유지

<template>
  <p>컴포넌트명: {{ currentCompName }}</p>
  <KeepAlive>
    <component :is="currentComp" />
  </KeepAlive>
  <button @click="switchComp">바꾸기</button>
</template>

<script setup lang="ts">
import AppInput from "~/components/AppInput.vue";
import AppRadio from "~/components/AppRadio.vue";
import AppSelect from "~/components/AppSelect.vue";

const currentComp = ref(AppInput);
const currentCompName = ref("Input");
const compList = [AppInput, AppRadio, AppSelect];
const compNameList = ["AppInput", "AppRadio", "AppSelect"];
const currentCompIndex = ref(0);

const switchComp = () => {
  currentCompIndex.value += 1;
  if (currentCompIndex.value >= 3) {
    currentCompIndex.value = 0;
  }
  currentComp.value = compList[currentCompIndex.value];
  currentCompName.value = compNameList[currentCompIndex.value];
};
</script>

 

'🎨 프론트엔드 공부/Vue & Nuxt' 카테고리의 다른 글
  • <Vue 3와 타입스크립트로 배우는 프런트엔드 개발> ch5 ~ ch7
  • <Vue 3와 타입스크립트로 배우는 프런트엔드 개발> ch1 ~ ch4
  • [Nuxt] 형제 컴포넌트끼리 데이터 공유하기
  • [Nuxt] composables, utils, types 폴더의 auto import 사용하기
지식물원
지식물원
지식이 자라는 식물원!
  • 지식물원
    지식물원
    지식물원
  • 전체
    오늘
    어제
    • 분류 전체보기 (516)
      • 🎨 프론트엔드 공부 (253)
        • JS & TS (92)
        • HTML & CSS (22)
        • React & Next (49)
        • Vue & Nuxt (22)
        • 기타 (68)
      • 🤓 기술 학습 & 공부 기록 (116)
        • Node.js (0)
        • Python (37)
        • 백엔드 (0)
        • 딥러닝 (1)
        • 컴퓨터 일반 (72)
        • 개발 인프라 (6)
      • 👨‍💻 프로젝트 경험 (6)
        • Work (0)
        • Toy (6)
      • ⚙️ 개발 팁 & 노하우 (21)
        • 프론트엔드 (6)
        • 기타 (15)
      • ☕️ 커리어 & 인터뷰 준비 (88)
        • 코딩 테스트 (88)
      • 📰 기술 트렌드 & 생각 정리 (4)
      • 📚 기타 (25)
        • 마케팅 (15)
        • 비개발서적 (10)
  • 블로그 메뉴

    • 태그
  • 링크

  • 공지사항

    • 모바일 접속 시 코드 하이라이팅 깨질 때
  • 인기 글

  • hELLO· Designed By정상우.v4.10.3
지식물원
<Vue 3와 타입스크립트로 배우는 프런트엔드 개발> ch8 ~ ch9
상단으로

티스토리툴바