Web/HTML&CSS

[SCSS] 05. At-Rules -02. @extend

은서파 2022. 10. 4. 00:19

다음으로 살펴볼 rule은 @extend이다. 이녀석은 sass가 주목받게 해준 강력한 rule이다. 그런데 개인적으로 상당히 까다로웠고 여기 저기서 사용을 지양해야한다는 글도 많다. 계륵(鷄肋) 같은 존재인가? 그래도 한번 정리해보자.

 

@extend

 

용도

@extend는 어떤 클래스가 다른 클래스의 모든 스타일과 함께 고유한 스타일을 가져야 하는 경우에 사용된다.

.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;​
}​

좌측의 SCSS는 .error가 정의되어있고 내부에 &--serious의 &는 외부의 선택자를 나타내기 때문에 .error--serious가 된다. 다시 .error--serious에서는 @extend .error가 있으므로 .erroe에 선언된 style인 border와 background-color를 가지는 블록과 고유한 스타일인 border-width를 갖는 블록이 생성된다.

@extend의 신기한 점은 스타일 뿐 아니라 대상의 관계가 가지는 제약사항까지 확장한다.(말이 좀 어렵네요)

.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;​
}​

좌측의 .error는 :hover를 가지고 있다. 그리고 .error--serious는 .error를 상속받는데 단지 .error의 css style 뿐 아니라 :hover 속성을 확장한다.

선택자 확장 방식

@extend는 여러 면에서 @mixin과 비교된다. @mixin이 현재의 style 규칙에 @mixin 내용을 복사해서 붙이는 반면 @extend는 style 규칙 뿐 아니라 selector의 확장 부분까지 가져온다. 이때 모든 SCSS 내용을 다 읽고 나서 구성된 관계의 제약사항을 고려하여 확장한다.

.c {​
  // 아직 .b가 없지만 다 읽고 나서 처리한다.​
  @extend .b;​
}​

// .b는 .a의 자식이다.​
.a>.b {​
  color: red;​
}​
.a > .b, .a > .c {​
  color: red;​
}​

SCSS를 살펴보면 .c는 @extend .b 하고 있는데 아직 .b는 등장하지 않았다. 두 번째 블록에서 .b는 .a의 자식으로 설정되어있다. .b가 존재하기 위해서는 .a가 반드시 필요한 것이다. 이런 내용을 다 읽은 후 @extend가 동작한다.

최종적으로 .c는 .b를 확장하므로 반드시 .a의 자식이어야 한다. 결과로 생성된 css에는 .a>.b와 .a>.c가 생성된다.

.content nav.sidebar {
  @extend .info;
}​

p.info {​
  background-color: #dee9fc;​
}​

.guide .info {​
  border: 1px solid rgba(#000, 0.8);​
  border-radius: 2px;​
}​

​
main.content .info {​
  font-size: 0.8em;​
}​

좀 더 복잡하게 적용된 확장룰을 살펴보자. 상세한 내용은 주석으으로 대체한다.

/* .content 자손 nav 태그 중 .sidebar가 .info를 확장하려 한다.*/
.content nav.sidebar {
  @extend .info;
}

/* 아래 내용은 확장에 포함되지 않는다. 
왜냐하면 nav중 sidebar가 확장하는데 아래 내용은 p 중 info이므로 상충된다.*/
p.info {
  background-color: #dee9fc;
}

/* nav.sidebar의 조상으로 .content가 있고 .info를 확장하다 보면 .guide도 있다.
 .content와 .guide의 선후 관계를 알 수 없으므로 
 .content .guide nav.sidebar와 .guide .content nav.sidebar 두개가 생성된다.*/
.guide .info {
  border: 1px solid rgba(#000, 0.8);
  border-radius: 2px;
}

/* 역시 main.content .content nav.sidebar와 .content main.content nav.sidebar가 생성될 수 있지만
 어치피 .content는 main.content에 부합하므로 main.content nav.sidebar만 생성된다.*/
main.content .info {
  font-size: 0.8em;
}
개인적으로 이렇게 복잡하게 써야할까? 쓸수 있을까 하는 생각이 들긴 한다.

 

place holder selector 사용

@mixin에서와 마찬가지로 @extend에도 placeholder selector(%)를 사용할 수 있다. 이녀석은 마치 java의 abstract class 처럼 상속 전용의 선택자로 단독으로 사용되지는 못한다.

.alert:hover,%strong-alert {
  font-weight: bold;
}
%strong-alert:hover {
  color: red;
}
.error {
  @extend %strong-alert;
}
.alert:hover, .error {
  font-weight: bold;
}

.error:hover {
  color: red;
}

좌측의 css 결과를 살펴보면 %strong-alert 내용은 보이지 않지만 @extend는 잘 동작하는 것을 볼 수 있다.

 

제약사항

 

다음은 @extend를 사용할 때의 제약사항에 대해서 알아보자.

compound selector는 확장 불가

@extend는 .info나 a 처럼 혼자 사용되는 간단한 selector들만 확장이 가능하며 .message.info와 같은 compuund selector는 확장할 수 없다.

.alert {
@extend .message.info;
// ^^^^^^^^^^^^^
// Error: Write @extend .message, .info instead.

@extend .main .info;
// ^^^^^^^^^^^
// Error: write @extend .info instead.
}

 

여기서 compound selector는 combinator( >, +, ~  등)으로 연결되지 않은 단순 선택자의 나열로 img#hero, a:focus 등의 형태를 말한다.

 

html 상속관계는 휴리스틱하게 처리한다.

complex selector를 확장할 때는 모든 상위 selector를 생성하지 않고 조상 selector들을 하나로 묶어서 처리한다. 

header .warning li {
  font-weight: bold;
}

aside .notice dd {
  @extend li;
}
header .warning aside .notice dd{}
header aside .warning .notice dd{}
header aside .notice .warning dd{}
aside .notice header .warning dd{}
aside header .notice .warning dd{}
aside header .warning .notice dd{}

좌측의 sass를 제대로 컴파일 한다면 li를 확장하는 dd의 입장에서 header, .warning, aside, .notice의 순서를 명확히 알기 어렵기 때문에 우측처럼 매우 다양한 조합의 선택자가 생성되어야 한다. 하지만 실제로 생성되는 css는 아래와 같다.

header .warning aside .notice dd{}

aside .notice header .warning dd{}

즉 [header .warning]과 [aside .notice]는 각각 묶여서 처리된다.

complex selector란 combinator로 연결된 하나 이상의 compound selector의 나열로 #hero img, a:focus > .icon 등의 형태를 말한다.
휴리스틱(heuristic)이란 어떤 문제를 단순화 시켜서 처리하는 것을 의미한다.

 

css at rule에서의 사용 제약

@media 등 css의 at-rule에서도 @extend를 사용할 수는 있지만 외부에 선언된 내용을 확장할 수는 없다.

@media screen and (max-width: 600px) {
  .inner {
    border: 1px #f00;
    background-color: #fdd;
  }
  .error--serious {
    @extend .inner; // 사용 가능
    @extend .outer; // 사용 불가
  }
}

.outer {
  border: 1px #f00;
  background-color: #fdd;
}

 

@extend vs @mixin

 

스타일을 캡슐화 하고 재사용한다라는 면에서 @extend와 @mixin은 상당히 포지셔닝이 겹친다. 그럼 언제 어느것을 사용하는 것이 좋을까?

일반적으로

  • argument를 전달해서 재구성하는 경우라면 mixin을 사용해야 한다.
  • style 덩어리를 전달해야 한다면
    • extend는 BEM 방법론등을 적용하여 의미론적으로 class를 작성해서 관계를 표현했을 때 유리하다.
    • 비 의미론적인 경우는 단순히 mixin을 사용하는 것이 훨씬 직관적으로 CSS를 구성할 수 있다.

 

extend 비추?

@extend는 SASS에 적용된 획기적인 문법으로 세간의 주목을 정말 많이 받았지만 인터넷에는 extend의 사용에 부정적인 글들이 많다.

 

개인적으로 그냥 개인적으로 좀 과하다? 라는 생각이 들기도 한다. BEM도 그렇고..

https://goodteacher.tistory.com/570

 

[BEM] BEM method0logy

SCSS를 정리하면서 CSS 코드를 보다보니 클래스 이름을 매우 번거롭게 _를 두개씩 써가면서 작성해놨길래 이상하다 싶어서 찾아봤는데 BEM이라는 방법론에 입각한 것이었다! 이번 포스트에서는 BEM

goodteacher.tistory.com