본문 바로가기
Study

[Vue.js] Vue.js Style - CSS scoped vs CSS module

by 안자두 2023. 5. 26.

SFC에서의 CSS

Single File Component로 개발을 하면 생성한 .vue 파일에는 각각의 코드 블록에 HTML, JavaScript, CSS를 작성할 수 있다. 그중, CSS는 Style이라는 코드 블록에 작성을 한다. 각 .vue 파일은 여러 개의 style 태그를 포함할 수 있다.

style 태그는 현재 컴포넌트에 스타일을 캡슐화하는 데 도움이 되도록 scoped 또는 module 속성을 가질 수 있다. 캡슐화를 통해 다른 컴포넌트들과의 중첩을 방지할 수 있다.

두 방법 모두 캡슐화를 할 수 있기 때문에 동일하게 느껴지지만 다른 점이 있다.
아래서는 이 차이점에 대해 알아보도록 하겠다.


scoped

style 태그에 scoped 속성을 추가함으로써 사용할 수 있다. 해당 CSS는 현재 컴포넌트의 엘리먼트에만 적용이 된다. 

<template>
  <div class="test">
    <slot />
  </div>
</template>

<style scoped>
.test {
  margin: 40px;
  border: 3px solid salmon;
}
</style>

단, 자식 컴포넌트 중 루트 노드는 부모의 범위가 지정된 CSS와 자식 범위가 지정된 CSS 모두의 영향을 받는다. 이것은 부모가 레이아웃 목적으로 자식 루트 엘리먼트의 스타일을 지정할 수 있도록 의도적으로 설계된 것이다.

조금 헷갈려서 직접 예시를 만들어 비교해 보았다.

main 페이지에서 스타일이 적용된 A를 호출한 다음, 내부에 A의 스타일을 사용하고 있는 B와 C를 중첩할 것이다.

<!-- main -->
<template>
  <div style="width: 500px; margin: 0 auto">
    <h2>border example</h2>
    <A>
      <C></C>
      <B>
        <B>
          <C></C>
        </B>
      </B>
    </A>
  </div>
</template>

<script>
import A from './css-modules/cssAScoped.vue';
import B from './css-modules/cssDefault2.vue';
import C from './css-modules/cssDefault3.vue';

export default {
  components: {
    A, B, C
  }
}
</script>
<!-- A -->
<template>
  <div class="A">
    scoped A
    <slot />
  </div>
</template>

<style scoped>
.A {
  padding: 40px;
  border: 3px solid salmon;
  color: yellowgreen;
  font-size: 20px;
}

.B {
  padding: 40px;
  border: 3px solid rgb(250, 241, 114);
  color: rgb(50, 205, 179);
  font-size: 20px;
}
.C {
  padding: 40px;
  border: 3px solid rgb(209, 250, 114);
  color: rgb(50, 117, 205);
  font-size: 20px;
}
</style>
<!-- B -->
<template>
  <div class="B">
    default B
    <slot />
  </div>
</template>

<!-- C -->
<template>
  <div class="C">
    default C
    <slot />
  </div>
</template>

이 경우, 아래와 같은 결과나 나온다.

scoped 예제 1

B와 C 모두 루트 노드에서 스타일을 적용하고 있다. 때문에, A의 자식 중 가장 루트에 위치한 첫 번째 C와 첫 번째 B는 A의 스타일이 적용된 것을 볼 수 있다.
하지만, 첫 번째 B에 중첩된 두 번째 B와 두 번째 C의 경우에는 class로 지정해준 스타일이 아닌, 부모인 첫 번째 B의 스타일 색상만 상속받은 것을 볼 수 있다.

스타일이 있는 컴포넌트의 루트 자식만이 적용된 것을 볼 수 있다.

만약 B와 C에서 루트 노드가 아닌 곳에 적용하면 어떻게 될까?

<!-- B -->
<template>
  <section>
    <div class="B">
      default B
      <slot />
    </div>
  </section>
</template>

<!-- C -->
<template>
  <section>
    <div class="C">
      default C
      <slot />
    </div>
  </section>
</template>

B와 C 각각을 section 태그로 감싸 루트 노드가 아닌 곳에 스타일을 적용해 보았다.

scoped 예제 2

main 페이지에서 각 컴포넌트들의 위치는 그대로인데도 스타일이 적용되지 않은 것을 볼 수 있다.
(부모 속성이 적용되는 color 제외)

이로써 자식 컴포넌트의 루트 노드만이 영향을 받음을 확인할 수 있었다.

scoped를 사용하면서 부분적으로는 모든 자식 노드에 적용이 되도록 하려면 어떻게 해야할까?
이때는 deep 셀렉터를 사용할 수 있다.

두 번째 예제에서 A의 스타일 요소를 조금 수정해 보았다.

<!-- A -->
<template>
  <div class="A">
    scoped A
    <slot />
  </div>
</template>

<style scoped>
.A {
  padding: 40px;
  border: 3px solid salmon;
  color: yellowgreen;
  font-size: 20px;
}

.B {
  padding: 40px;
  border: 3px solid rgb(250, 241, 114);
  color: rgb(50, 205, 179);
  font-size: 20px;
}
:deep(.C) {
  padding: 40px;
  border: 3px solid rgb(209, 250, 114);
  color: rgb(50, 117, 205);
  font-size: 20px;
}
</style>

클래스 명이 C인 스타일에만 deep 셀렉터를 추가하여 클래스 명이 C인 모든 자식 노드에서 사용할 수 있도록 해주었다.

결과는 다음처럼 모든 C에 스타일이 적용된 것을 볼 수 있다.

 

 


 

module 

style module 태그는 CSS 모듈로 컴파일되고, 결과적으로 CSS 클래스를 $style 키 내부에 객체로 컴포넌트에 노출한다.
결과적으로 클래스는 충돌을 피하기 위해 해시되며, CSS 범위를 현재 컴포넌트로만 지정하는 것과 동일한 효과를 낸다.

사용법은 다음과 같다.

<template>
  <div :class="$style.test">
    <slot />
  </div>
</template>

<style module>
.test {
  margin: 40px;
  border: 3px solid salmon;
}
</style>

class 명을 지정할 때, :class="$style.클래스명"으로 지정해주어야 한다.

scoped에서 예시를 확인해 보았다. 그럼 module의 경우에는 위와 같은 상황일 때 어떻게 적용될까?

main 페이지는 첫 번째 예시와 동일하고 스타일 적용 방법만 수정해 보겠다.

<!-- A -->
<template>
  <div :class="$style.A">
    scoped A
    <slot />
  </div>
</template>

<style module>
.A {
  padding: 40px;
  border: 3px solid salmon;
  color: yellowgreen;
  font-size: 20px;
}

.B {
  padding: 40px;
  border: 3px solid rgb(250, 241, 114);
  color: rgb(50, 205, 179);
  font-size: 20px;
}
.C {
  padding: 40px;
  border: 3px solid rgb(209, 250, 114);
  color: rgb(50, 117, 205);
  font-size: 20px;
}
</style>
<!-- B -->
<template>
    <div :class="$style.B">
      default B
      <slot />
    </div>
</template>

<style module>
/*  */
</style>

<!-- C -->
<template>
    <div :class="$style.C">
      default C
      <slot />
    </div>
</template>

<style module>
/*  */
</style>

 

예상했듯, 하위 요소에는 적용이 되지 않는 것을 볼 수 있다.

module 예시

css가 적용되면 아래처럼 class 명이 변경된다.

 


 

scoped와 module

그렇다면 두 방법은 각각 언제 쓰는 것이 좋을까?

scoped의 경우에는 위에서 말했다시피 하위 컴포넌트에 레이아웃이 적용돼야 할 때 사용하는 것이 좋고, 상위 요소가 하위 요소에 적용이 안 되면 좋겠을 때, 완전히 독립적으로 작성하고 싶을 때는 module을 사용하는 것이 좋을 것 같다.

 


 

REF

https://ko.vuejs.org/api/sfc-css-features.html#scoped-css

 

728x90