Vue.js component+ (slots)

강정우·2023년 4월 1일
0

vue.js

목록 보기
18/72
post-thumbnail
post-custom-banner

슬롯

  • react의 children 포지션.

  • 개발자는 기본적으로 중복을 굉장히 싫어한다.
    그러면서 앞서 포스팅했던 내용을 보면 styling을 scoped로 지역으로 관리하면서 문제가 같은 코드를 반복하여 사용해야한다는 단점이 생겼다.

  • 하지만 이 slot을 이용하면 해당 컴포넌트와 함께 styling도 지정이 가능하다.

BaseCard.vue

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

<script>
export default {}
</script>

<style scoped>
div {
  margin: 2rem auto;
  max-width: 30rem;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
  padding: 1rem;
}
</style>

named slots

  • 프로젝트를 작성하다보면 어떠한 UI컴포넌트안에 또 다른 컴포넌트들이 들어있을 경우가 있다.
    이는 simple하게 <slot>을 2번 쓰면 되는데 문제는 vue가 어떤 children이 어떤 <slot>으로 들어가야하는지를 모른다는 것이다.

  • 그래서 slot에 이름을 지정해주어야한다.

BaseCard.vue

<template>
  <div>
    <header>
      <slot name="header"></slot>
    </header>
    <slot></slot>
  </div>
</template>

<script>
export default {}
</script>

<style scoped>
div {
  margin: 2rem auto;
  max-width: 30rem;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
  padding: 1rem;
}
</style>
  • 이때 name속성으로 <slot>을 지정해주면 된다. 그리고 굳이 모든 <slot>에 이름을 지정해줄 필요는 없다. 이름이 지정이 안 된 <slot>이 있다면 그것은 자동으로 default <slot>이 된다.
    그리고 당연하게 default <slot>은 1개만 있어야한다.

App.vue

<template>
    <BaseCard>
      <template v-slot:header>
        <h3>{{ fullName }}</h3>
        <base-badge :type="role" :caption="role.toUpperCase()"></base-badge>
      </template>
      <template v-slot:default>
        <p>{{ infoText }}</p>
      </template>
      <slot></slot>
    </BaseCard>
    ...
</template>
  • 사용하는 html 코드에서는 해당 컴포넌트에 v-slot 속성을 추가하여 <slot>에 설정한 이름값을 부여하면 연결된다.

  • 물론 v-slot:default를 지정주지 않아도 되지만 가독성과 코드 이해력을 높이기 위해 작성하였다. 이때 잘 못 이해하면 안되는 부분이 있는데
    default를 <slot>으로 이해해선 안된다는 것이다.
    무슨말이냐면

  • default는 이름(name)이 없는 슬롯을 의미하는 것이 아니고 수신하는 콘텐츠가 없는 슬롯에 default 콘텐츠를 렌더링할 수 있다는 의미이다.

  • 이때 <template>는 </>처럼 아무것도 렌더링하지 않기 때문에 wapper로 쓰기 좋다.

#

  • #는 v-slot:의 축약어이다.
<template>
    <BaseCard>
      <template #header>
        <h3>{{ fullName }}</h3>
        <base-badge :type="role" :caption="role.toUpperCase()"></base-badge>
      </template>
      <template #default>
        <p>{{ infoText }}</p>
      </template>
      <slot></slot>
    </BaseCard>
    ...
</template>

슬롯 스타일링

  • 당연하게도 슬롯 밖에서 스타일링을 해도 슬롯까지 영향을 미치진 않는다. 왜그럴까?

  • Vue.js는 다른 컴포넌트에 콘텐츠를 보내기 전에 템플릿을 분석하고 컴파일하고 평가한다.
    따라서 UserInfo.vue 내부에서 정의된 모든 항목에 액세스할 수 있고 여기에 정의된 스타일링도 해당 컴포넌트의 <template>에 영향을 준다. 하지만 콘텐츠를 보내는 컴포넌트의 마크업에는 적용되지 않는다.

슬롯 기본값 설정하기

BaseCard.vue

<template>
  <div>
    <header>
      <slot name="header">
        <div>default header</div>
      </slot>
    </header>
    <slot></slot>
  </div>
</template>
  • 템플릿에 위와같이 slot name 태그 안에 어떠한 값을 설정해두면, 위 BaseCard.vue를 사용하는 곳에 같은 위치에 children이 있다면 그 요소를 보여주고 children이 없다면 설정해둔 기본값(default)이 보인다는 것이다.

  • 그럼 굳이 default 값을 설정해야하는 이유는 무엇일까?

    • sementic 하지 못하기 때문이다.

  • 위 사진을 보다시피 header 태그는 존재하나 내부에 아무것도 없는 div가 생겨버린다.

슬롯 값 접근하기 ($slots)

<script>
mounted : {
	console.log(this.$slots.네임속성값)
}
</script>
  • 즉, 해당 값에 접근할 수 있다면 해당 값으로 v-if로 동적 렌더링을 할 수 있다는 것이다.

  • 그렇게 된다면 빈 html 파일이 나올 염려도 줄게 된다.

BaseCard.vue

<template>
  <div>
    <header v-if="this.$slots.header">
      <slot name="header">
        <div>default header</div>
      </slot>
    </header>
    <slot></slot>
  </div>
</template>

즉, $slots를 사용해서 특정 슬롯에 대한 데이터를 수신하는지 확인하고 아니라면 해당 정보를 사용해서 특정 요소를 렌더링하지 않을 수 있다.

scoped slots

  • 이 개념이 등장한 이유는 <slot>이 사용된 커스텀 태그와 해당 커스텀 태그를 마크업 한 부분에 데이터를 보내기 위해 나온 개념이다.

커스텀 태그

<template>
  <ul>
    <li v-for="goal in goals" :key="goal">
      {{goal}}
    </li>
  </ul>
</template>
  • 여기서 {{goal}} 부분에 goal 데이터에 뭔가 다양한 마크업을 하고싶을 때

커스텀 태그

<template>
  <ul>
    <li v-for="goal in goals" :key="goal">
      <slot :item="goal" another-props="..."></slot>
    </li>
  </ul>
</template>

마크업 태그

...
    <CourseGoals>
      <template #default="slotProps">
        <h2>{{ slotProps.item }}</h2>
        <p>{{ slotProps['another-props'] }}</p>
      </template>
    </CourseGoals>
...
  • 데이터에 접근하기 위해 <slot>에 대한 마크업을 template로 감싼다. 슬롯에 보내고 싶은 마크업을 감싸는 것이다.
    단, <slot> 내부에 추가 element가 없다면 <template>를 쓰지 않고 부모태그에 써도 된다. 여기서는 <CourGaols>에 #default 속성을 써도 된다는 뜻이다.

  • 위 코드중 "slotProps"의 값은 언제나 객체이다.
    커스텀 태그 <slot>에서 정의한 모든 속성값이 병합된 객체이다.

  • JavaScript 코드로 유효하지 않은 점 표기법을 사용할 수 없기 때문에 대괄호로 표기하여 대시를 넣은 이름도 동작하게 하였다.

PS. 나는 추가 속성값을 kebab-case 가 아닌 carmelCase로 넣었더니 동작하였다.

profile
智(지)! 德(덕)! 體(체)!
post-custom-banner

0개의 댓글