Svelte 에서 하위 컴포넌트에 스타일 입히는 방법

Composite·2022년 2월 4일
0

음... 일단 리액트봐 뷰와 달리 스벨트는 스타일 태그의 범위를 해당 컴포넌트 내로 엄격하게 제한하고 있다. 이유는 이슈 코멘트에 적혀 있는데, 번역은 시간나면 여기에 내용 추가해서 기재하도록 하겠다.
일단, 리액트나 뷰와 달리 스벨트는 유연한 스타일링을 지원하지 않는다.
따라서 현재 지원하는 범위 내의 방법을 써야 한다.

자식 컴포넌트 내 스타일링 사용

일단 자식 컴포넌트에 class 속성을 부여하면, 스타일은 부모 스타일이 아닌, 컴포넌트 스타일을 따르게 된다. 예를 들면,

<script>
import Child from './Child.svelte'
</script>
<style>
.my-style {
  color: red;
}
</style>

<Child class="my-style" />

이렇게 작성할 경우, 아래와 같이 생성된다.

<script>
import Child from './Child.svelte'
</script>
<style>
.my-style.svelte-PARENTHASH {
  color: red;
}
</style>

<Child class="my-style svelte-CHILDHASH" />

즉, 부모 컴포넌트의 해시 따로, 자식 컴포넌트의 해시 따로 생성되어, 자식 컴포넌트의 스타일은 자식 내에서만 유효하다는 점. REPL 보기
이렇게 하면 문제가 뭐냐. 간단하다. 스타일 지정을 자식 내에서만 가능하다 보니 너무나 한정적이라 유연하지가 않다는 거. 하지만 방법이 없는 건 아니다.
그렇다고 전역 CSS가 안먹히는 건 아니니 공통 CSS 클래스(예: tailwind 같은 별도의 css 파일)는 걱정 말고 쓰도록 하자.

자식 컴포넌트의 스타일링 평탄화

불행 중 다행히도, 스벨트는 스코프 범위를 벗어나는 유일한 가상 선택자를 지원하는데, :global 선택자이다. 이건 리액트에서 그다지 쓸 일은 없지만 뷰에서는 간간히 보일 것이다. 왜냐면 뷰는 스코프 스타일링을 지원하기 때문이다. 뷰로 치자면 스벨트는 <style scoped> 기능만 지원한다고 보면 된다.

예를 들면,

<script>
import Child from './Child.svelte'
</script>
<style>
:global(.child-style) {
  color: red;
}
</style>

<Child class="child-style" />

이렇게 하면 스벨트 컴파일러는 스타일을 이렇게 해석해서 도출한다.

<script>
import Child from './Child.svelte'
</script>
<style>
.child-style {
  color: red;
}
</style>

<Child class="child-style svelte-CHILDHASH" />

.child-style 클래스는 바뀌지 않았지만, 자식 컴포넌트의 class 속성은 뭐가 추가됐 게 문제다. 이제 이걸 해결하기 위해 평탄화 작업을 진행해보자.

자식 컴포넌트를 아래와 같이 세팅하면 끝.

<script>
let cssClass = ''
export { cssClass as class }
</script>
<p class={cssClass || ''}>자식 컴포넌트</p>

근데 class는 예약어라서 몇몇 linting 도구에서 오류가 날 수도 있다. 하지만 동작은 되니 안심하라. 이게 다다. 자식 컴포넌트의 class 속성을 동적 문자열로 줬기 때문에, 스벨트 컴파일러는 여기까지 관여할 능력이 되지 않는다. 따라서 클래스명 그대로 따르게 되기 때문에 동작할 것이다. REPL 보기

추가: 더 간단하게 스벨트 내부 속성$$restProps 속성을 사용해서 해결할 수 있다. 자식 컴포넌트를 이렇게 작성해도 손쉽게 해결한다.

<p class={$$restProps.class || ''}>자식 컴포넌트</p>

만약 linting 태클이 걱정된다면 이렇게 작성해도 무관하다.

<p class={$$restProps['class'] || ''}>자식 컴포넌트</p>

둘 중 하나 linting 도구가 태클걸지 않는 방법을 쓰면 된다.

크린랲

자식 컴포넌트의 편법을 사용하지 않는 방법이 있다. 대신 CSS 클래스를 포기해야 한다. 클래스명을 포기하는 방법이 있지만, 태그, 속성 등 클래스 선택자를 제외한 나머지를 쓰면 된다. 예를 들면 간단하게 태그명만으로 퉁칠 수 있다.

<script>
	import Child from './Child.svelte'
</script>

<style>
	.container > :global(p) {
		color: green;
	}
	
	:global(p) {
		color: orange
	}
</style>

<div class="container">
	<Child/>
</div>

<Child/>

대신 문제가 있는데, 자식 컴포넌트가 컴파일 된 HTML 태그명 등의 후처리된 선택자를 사용해야 한다는 점이다. :global 선택자니 뭐니 모두 컴포넌트를 태그 선택자로 지원하지 않기 때문이다. 당연한 것 아니겠는가? 컴파일러가 무시하고 도출시키기 때문에 컴파일한 결과에 자식 컴포넌트명이 사라지는 건 당연지사. 이럴 때 태그명 말고도 속성명을 쓴다던가 이런 방법도 있는데, 클래스명 선택자를 못 쓰니 캐시 성능이 떨어지는 건 어쩔 수 없다. 그래서 특별한 일 아니면 왠만하면 최소한 태그명은 써주는 것이 좋다. 이후에 속성 선택자를 쓰든 부가적인 어떤 선택자를 쓰든 상관없다.
대신 위에 평탄화에 언급한 자식 컴포넌트에서 편법을 안 써도 된다는 장점이 있다.

도와줘요, 모듈!

이것도 싫고 저것도 싫은 사람을 위해, 모듈로 해결하는 방법을 소개한다.
Github Comment

대충 과정은 이렇다.

  1. $ npm i -D svelte-preprocess-cssmodules svelte-as-markup-preprocessor
    yarnpnpm 쓰면 거기에 맞게 깔면 된다. 참고로 스벨트는 pnpm 권장.
  2. svelte.config.js 파일의 preprocessor 기능을 개조한다.
  3. Child 컴포넌트 작성
  4. 부모 컴포넌트 작성

음... 글쎄다. 다시보니 그다지... CSS 클래스 유연화를 위해 이렇게까지 해야 하는지 개인적으로 의문이 남아서 여기까지.

끗.

profile
지옥에서 온 개발자

0개의 댓글