맨틀 이야기
Vue SFC 컴포넌트의 CSS 적용 방식 본문
Vue 3 버전 기준, Vue의 컴포넌트는 SFC(Single File Components)라고 부르는 하나의 *.vue 파일에 만들어 활용할 수 있다.
SFC 파일에는 자바스크립트의 동적 기능, 페이지에 렌더링할 HTML DOM 태그, 그리고 CSS 스타일링까지 한꺼번에 작성해서 하나의 독자적인 컴포넌트로 사용할 수 있는데, 우연히 이런 SFC 컴포넌트 내에서 CSS 스타일을 적용할 때 유의해야 하는 점을 알게 됐다.
SFC 컴포넌트에서 <style> 태그를 사용할 때, 특정 클래스명에 스타일을 부여하면 해당 클래스명을 사용하는 외부 모든 컴포넌트들에도 스타일이 적용된다. 각 .vue 파일에 선언할 수 있어서 해당 파일이 렌더링하는 요소들에게만 적용되는 고유한 스타일이라고 생각했는데, 사실은 아니었던 것이다. <style> 태그는 글로벌하게 적용되기 때문에 클래스명을 정리정돈하지 않고 BEM 방식도 따르지 않았던 나는 어디선가 중첩된 CSS 스타일을 찾아 헤매는 값진 경험을 하게 됐다...
만약 컴포넌트 내 고유한 스타일로 사용하고 싶다면 scoped라는 키워드를 추가해 해당 컴포넌트의 스코프 내에서만 CSS를 적용하도록 설정할 수 있다. <style> 대신 <style scoped> 태그를 사용하면 여러 컴포넌트에 동일한 클래스명을 지어줘도 각각 다른 스타일이 적용된다.
다만 이 scoped 키워드를 붙일 경우 해당 컴포넌트의 <template> 태그 안에 렌더링되는 모든 컴포넌트들이 scoped 스타일을 그대로 물려받게 된다. 아래처럼 코드를 짰을 때,
부모 컴포넌트 | 자식 컴포넌트 | 형제 컴포넌트 |
<template>
<div class="container">
hello, world (parent)
</div>
<ChildComponent />
</template>
<style scoped>
.container {
color: royalblue;
}
</style>
|
<template>
<div class="container">
hello, world (child)
</div>
</template>
<style scoped>
.container {
color: goldenrod;
}
</style>
|
<template>
<div class="container">
hello, world (sibling)
</div>
</template>
<style scoped>
.container {
color: purple;
}
</style>
|
위 컴포넌트들을 렌더링하는 페이지:
<template>
<div>
<ParentComponent />
<SiblingComponent />
</div>
</template>
렌더링 결과:
자식 컴포넌트의 폰트에는 금색을 지정해줬는데, 확인해보니 부모와 같은 파란색으로 렌더링됐다. 브라우저의 개발자툴을 보면 자식 컴포넌트의 스타일이 오버라이팅된 것이 보인다.
Vue 앱은 DOM 트리를 생성하는 과정에서 scoped 스타일을 사용하는 컴포넌트의 HTML 태그에 유니크한 data 표식을 추가하는 것으로 보인다. (scoped 키워드가 없는 경우 표식을 생성하지 않음) 따라서 같은 <template> 내에 렌더링되는 태그들은 공통된 data 표식을 갖게 되고, 그에 더불어 동일한 클래스명을 사용해 CSS 스타일을 조작할 경우 상위 컴포넌트의 스타일이 우선순위를 갖는다.
부모 컴포넌트의 스타일이 우선순위를 갖기 때문에, 자식 컴포넌트의 클래스명을 부모와 중복되게 지을 경우 부모의 스타일을 따라가게 된다. CSS 요소가 겹치지만 않는다면 문제될 게 없지만, 그 반대의 경우 중첩된 컴포넌트들을 하나씩 들춰봐야 하는 상황이 발생할 수 있다.
CSS 모듈과 styled-components 라이브러리를 사용하면서 어느덧 고유 스타일링이 당연히 적용된다고 생각하게 된 것 같다. 프론트엔드 프레임워크들이 비슷한 개념을 토대로 진화해온 것을 자주 봐서 더더욱 그런 생각이 들었을 수도 있다. 코드를 작성할 때 염두에 두고 있어야 할 컨벤션에 대해 다시 생각해 본 계기가 됐다.
'Frontend' 카테고리의 다른 글
Konva 배우기 (1) (1) | 2024.07.12 |
---|---|
Vue 달력 라이브러리 v-calendar의 버그와 대안책 (0) | 2024.07.10 |
Vue 배우기 (3) (0) | 2024.06.06 |
Clerk metadata로 어드민 계정 롤 부여하기 (0) | 2024.06.05 |
Next.js: Clerk로 로그인 플로우 구현 (0) | 2024.05.31 |