@extend: 선택자 상속으로 스타일 물려받기
@extend: 선택자 상속으로 스타일 물려받기
페이지를 설계하다 보면 어떤 클래스가 다른 클래스의 스타일을 전부 가져가면서, 자기만의 스타일도 곁들여야 할 때가 있어요. 예를 들어 BEM 방법론은 블록·요소 클래스와 같은 요소에 붙는 수식(modifier) 클래스를 권장하죠. 그런데 클래스를 두 개 다 넣어야 하는 HTML은 지저분해지고, 하나를 빼먹는 실수도 생기기 쉬워요. Sass의 @extend 규칙이 이 문제를 풀어 줍니다. @extend <선택자>로 쓰면, 한 선택자가 다른 선택자의 스타일을 물려받게 돼요.
기본 사용
먼저 이런 HTML과 CSS가 있다고 볼게요.
<div class="error error--serious">
Oh no! You've been hacked!
</div>
.error {
border: 1px #f00;
background-color: #fdd;
}
.error--serious {
border-width: 3px;
}
error--serious가 error의 스타일까지 갖게 하고 싶다면, @extend .error로 상속하면 됩니다.
.error {
border: 1px #f00;
background-color: #fdd;
&--serious {
@extend .error;
border-width: 3px;
}
}
.error, .error--serious {
border: 1px #f00;
background-color: #fdd;
}
.error--serious {
border-width: 3px;
}
클래스 하나가 다른 클래스를 extend하면, Sass는 확장하는 선택자와 일치하는 모든 요소를 마치 확장되는 클래스에도 일치하는 것처럼 스타일링해요. .error--serious로 class="error--serious"만 써도, class="error"도 가진 것처럼 스타일이 적용되도록 해 주는 거예요.
선택자는 스타일 규칙에서 혼자만 쓰이지는 않죠. Sass는 선택자가 쓰인 곳이라면 어디서든 확장을 적용하는 걸 알고 있어요. 덕분에 요소가 확장된 선택자와 일치하는 것처럼 정확히 스타일링됩니다.
.error:hover {
background-color: #fee;
}
.error--serious {
@extend .error;
border-width: 3px;
}
.error:hover, .error--serious:hover {
background-color: #fee;
}
.error--serious {
border-width: 3px;
}
extend는 나머지 스타일시트가 컴파일된 후에 해결된다는 점을 알아두세요. 특히 부모 선택자(&)가 해결된 뒤에 일어나요. 그래서 .error { &__icon { ... } }처럼 중첩된 안쪽 선택자는 @extend .error의 영향을 받지 않습니다.
어떻게 동작하나
스타일을 현재 스타일 규칙에 복사하는 믹스인과 달리, @extend는 확장되는 선택자를 담은 스타일 규칙을 그 확장하는 선택자도 담도록 갱신해요. 선택자를 확장할 때 Sass는 *지능적 통합(intelligent unification)*을 수행합니다.
- 절대 일치할 수 없는
#main#footer같은 선택자는 만들지 않아요. - 복잡한 선택자는 HTML 요소가 중첩된 순서와 무관하게 동작하도록 교차시킵니다.
- 확장하는 선택자의 특이성(specificity)보다 작아지지 않는 선에서, 중복 선택자를 최대한 줄여요.
- 어떤 선택자가 다른 선택자의 모든 것을 일치시키는지 알고, 그것들끼리 합쳐 줍니다.
- 결합자(combinator), 유니버설 선택자, 선택자를 담는 가상 클래스를 지능적으로 처리해요.
.content nav.sidebar {
@extend .info;
}
// This won't be extended, because `p` is incompatible with `nav`.
p.info {
background-color: #dee9fc;
}
// There's no way to know whether `<div class="guide">` will be inside or
// outside `<div class="content">`, so Sass generates both to be safe.
.guide .info {
border: 1px solid rgba(#000, 0.8);
border-radius: 2px;
}
// Sass knows that every element matching "main.content" also matches ".content"
// and avoids generating unnecessary interleaved selectors.
main.content .info {
font-size: 0.8em;
}
p.info {
background-color: #dee9fc;
}
.guide .info, .guide .content nav.sidebar, .content .guide nav.sidebar {
border: 1px solid rgba(0, 0, 0, 0.8);
border-radius: 2px;
}
main.content .info, main.content nav.sidebar {
font-size: 0.8em;
}
@extend가 확장되는 선택자를 담은 스타일 규칙을 갱신하기 때문에, 그 스타일의 캐스케이드 우선순위는 @extend가 쓰인 위치가 아니라 확장되는 선택자의 스타일 규칙이 나타난 위치를 기준으로 정해져요. 다소 헷갈릴 수 있는데, 확장된 클래스를 HTML에 직접 넣었다면 그 규칙들이 가질 우선순위와 같다고 기억하면 돼요.
자리표시자 선택자(Placeholder Selectors)
오직 확장되기 위해서만 쓰는 스타일 규칙이 필요할 때가 있어요. 그럴 때는 %로 시작하는 자리표시자 선택자를 쓸 수 있어요. 자리표시자를 포함한 선택자는 CSS 출력에 포함되지 않고, 그것을 확장한 선택자만 CSS에 나타납니다.
.alert:hover, %strong-alert {
font-weight: bold;
}
%strong-alert:hover {
color: red;
}
.alert:hover {
font-weight: bold;
}
자리표시자 선택자도 모듈 멤버처럼 이름을 -나 _로 시작하면 비공개로 표시할 수 있어요. 비공개 자리표시자는 그것을 정의한 스타일시트 안에서만 확장할 수 있고, 다른 스타일시트에는 존재하지 않는 것처럼 보입니다.
확장 범위
어떤 스타일시트가 선택자를 확장하면, 그 확장은 상류(upstream) 모듈, 즉 그 스타일시트가 @use나 @forward로 불러오는 모듈과 그 모듈이 다시 불러오는 모듈들에 써 있는 스타일 규칙에만 영향을 줘요. @extend가 쓰는 시점에 알고 있던 스타일에만 영향을 주게 되어 예측 가능해집니다. 참고로 @import를 쓰면 확장 범위가 전혀 제한되지 않고 전역이 됩니다.
필수/선택 extend
보통 @extend가 스타일시트의 어떤 선택자와도 일치하지 않으면 Sass는 오류를 냅니다. 오타나, 상속하는 선택자는 그대로 두고 선택자 이름만 바꾸는 실수를 막아 주는 거예요. 확장되는 선택자가 반드시 존재해야 하는 extend를 *필수(mandatory)*라고 해요. 만약 확장되는 선택자가 없어도 아무 일도 하지 않게 하고 싶다면 끝에 !optional을 붙이면 됩니다.
extend vs mixin
extend와 믹스인은 모두 Sass에서 스타일을 묶고 재사용하는 방법이라, 언제 어떤 걸 써야 할지 자연스럽게 궁금해져요. 인자로 스타일을 설정해야 한다면 믹스인이 명백히 필요해요. 그런데 그냥 스타일 묶음 하나라면 어떨까요?
경험상, 의미 있는 클래스(또는 다른 의미적 선택자) 간의 관계를 표현할 때는 extend가 최선이에요. .error--serious 클래스를 가진 요소는 곧 error이므로 .error를 확장하는 게 맞죠. 하지만 의미 없는 스타일 모음이라면 믹스인을 쓰는 편이 캐스케이드 문제를 피하고 나중에 설정하기도 쉬워요.
대부분의 웹 서버는 CSS를 압축하는데, 이 압축 알고리즘은 반복되는 같은 텍스트 덩어리를 아주 잘 처리해요. 그래서 믹스인이 extend보다 CSS를 더 많이 만든다고 해도 사용자가 내려받을 양이 크게 늘어나지 않는 경우가 많습니다. CSS를 가장 적게 만드는 쪽보다, 당신의 쓰임에 더 맞는 기능을 고르면 돼요.
제한 사항
@media등 CSS at-rule 안에서@extend는 허용되지만, 그 at-rule 밖에 나타나는 선택자는 확장할 수 없어요. 확장하는 선택자가 주어진 미디어 맥락 안에서만 적용되기 때문에, 생성된 선택자에 그 제약을 보존할 방법이 스타일 규칙 전체를 복제하는 것뿐이거든요.- 복잡한 선택자를 교차할 때, Sass는 조상 선택자의 모든 가능한 조합을 만들지 않아요. 대신 각 선택자의 조상이 다른 선택자의 조상과 섞이지 않고 자급자족한다고 가정하는 휴리스틱을 씁니다. 실제 HTML과 일치할 가능성이 낮은 조합을 전부 만들면 스타일시트가 가치 대비 너무 커져서예요.