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>
이 경우, 아래와 같은 결과나 나온다.

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 태그로 감싸 루트 노드가 아닌 곳에 스타일을 적용해 보았다.

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>
예상했듯, 하위 요소에는 적용이 되지 않는 것을 볼 수 있다.

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

scoped와 module
그렇다면 두 방법은 각각 언제 쓰는 것이 좋을까?
scoped의 경우에는 위에서 말했다시피 하위 컴포넌트에 레이아웃이 적용돼야 할 때 사용하는 것이 좋고, 상위 요소가 하위 요소에 적용이 안 되면 좋겠을 때, 완전히 독립적으로 작성하고 싶을 때는 module을 사용하는 것이 좋을 것 같다.
REF
https://ko.vuejs.org/api/sfc-css-features.html#scoped-css
'Study' 카테고리의 다른 글
[Vue.js] Vuex (feat. Flux pattern) (0) | 2023.05.28 |
---|---|
[Vue.js] Vue Router (Nested Router vs Named Views) (0) | 2023.05.27 |
[Vue.js] Single File Component: SFC (0) | 2023.05.25 |
[Vue.js] Vue Template (v-bind, v-on, v-if, v-show) (0) | 2023.05.25 |
[Vue.js] Vue (MVVM, Props와 Event, v-model) (0) | 2023.05.24 |