@extend: 선택자 상속으로 스타일 물려받기

@extend: 선택자 상속으로 스타일 물려받기

페이지를 설계하다 보면 어떤 클래스가 다른 클래스의 스타일을 전부 가져가면서, 자기만의 스타일도 곁들여야 할 때가 있어요. 예를 들어 BEM 방법론은 블록·요소 클래스와 같은 요소에 붙는 수식(modifier) 클래스를 권장하죠. 그런데 클래스를 두 개 다 넣어야 하는 HTML은 지저분해지고, 하나를 빼먹는 실수도 생기기 쉬워요. Sass의 @extend 규칙이 이 문제를 풀어 줍니다. @extend <선택자>로 쓰면, 한 선택자가 다른 선택자의 스타일을 물려받게 돼요.

출처: Sass: @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--seriouserror의 스타일까지 갖게 하고 싶다면, @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--seriousclass="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과 일치할 가능성이 낮은 조합을 전부 만들면 스타일시트가 가치 대비 너무 커져서예요.

더 알아보기