การสืบทอด (Inheritance) ใน SASS

การสืบทอด (Inheritance) ใน SASS

บทความนี้อธิบายเกี่ยวกับการสืบทอดใน SASS

เราจะอธิบายการสืบทอดใน SASS พร้อมตัวอย่างที่ใช้งานจริง

YouTube Video

การสืบทอด (Inheritance) ใน SASS

การสืบทอดใน SASS (@extend) คือกลไกที่ช่วยให้เราสามารถนำสไตล์ของ selector หนึ่งไปใช้กับอีก selector หนึ่ง โดยไม่เกิดความซ้ำซ้อน เนื่องจากสไตล์เดียวกันจะถูก 'รวม' และส่งออกสำหรับองค์ประกอบหลายตัวใน markup จึงช่วยลดความซ้ำซ้อนใน CSS ได้ อย่างไรก็ตาม หากใช้อย่างไม่ระมัดระวัง อาจทำให้ selector ถูกควบรวมอย่างไม่ตั้งใจได้

พื้นฐาน: วิธีใช้ @extend

ด้านล่างคือตัวอย่างพื้นฐานที่ .btn--primary สืบทอดสไตล์จาก .btn @extend เป็นคำสั่งที่ใช้ขยาย selector เป้าหมาย

 1// Base button styles
 2.btn {
 3  padding: 0.5rem 1rem;
 4  border-radius: 4px;
 5  border: 1px solid #ccc;
 6  background: white;
 7  color: #333;
 8}
 9
10/* Primary button extends the base .btn */
11.btn--primary {
12  @extend .btn;
13  background: #007bff;
14  color: white;
15}
  • ด้วยการใช้ @extend .btn--primary จะสืบทอดสไตล์พื้นฐานของ .btn และเขียนทับเฉพาะส่วนที่จำเป็น

CSS ที่ได้จากการประมวลผล

 1.btn, .btn--primary {
 2  padding: 0.5rem 1rem;
 3  border-radius: 4px;
 4  border: 1px solid #ccc;
 5  background: white;
 6  color: #333;
 7}
 8
 9.btn--primary {
10  background: #007bff;
11  color: white;
12}

แนวทางที่ดีที่สุด: การใช้ Placeholder (%placeholder)

ตัวเลือก Placeholder (%name) คือเซเลกเตอร์ที่ไม่ได้ถูกแปลงออกมาเป็น CSS ตัวเลือกเหล่านี้ถูกใช้อย่างแพร่หลาย โดยเฉพาะเมื่อคุณต้องการแบ่งปันสไตล์ทั่วไปอย่างปลอดภัยสำหรับการสืบทอดร่วมกันระหว่างหลายคอมโพเนนต์

 1// %placeholder will not be output directly
 2%card-base {
 3  padding: 1rem;
 4  border-radius: 6px;
 5  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
 6  background: #fff;
 7}
 8
 9/* Components extend the placeholder */
10.card {
11  @extend %card-base;
12  border: 1px solid #eee;
13}
14
15.panel {
16  @extend %card-base;
17  border: 1px solid #ddd;
18}
  • ทั้ง .card และ .panel สืบทอดจาก %card-base ทำให้ทั้งคู่ใช้สไตล์ร่วมกันได้และเพิ่มเติมความแตกต่างได้ตามต้องการ

CSS ที่ได้จากการประมวลผล

 1.card, .panel {
 2  padding: 1rem;
 3  border-radius: 6px;
 4  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
 5  background: #fff;
 6}
 7
 8.card {
 9  border: 1px solid #eee;
10}
11
12.panel {
13  border: 1px solid #ddd;
14}

การสืบทอดแบบหลายแหล่ง (Multiple @extend)

คุณสามารถสืบทอด Placeholder หรือคลาสได้หลายรายการพร้อมกัน แม้ว่าจะช่วยให้สามารถนำสไตล์มาใช้ซ้ำได้ดีขึ้น แต่สิ่งสำคัญคือควรติดตามว่าแต่ละกฎถูกผสมกับเซเลกเตอร์ใดบ้าง

 1%btn-base {
 2  display: inline-block;
 3  padding: 0.5rem 1rem;
 4  border-radius: 3px;
 5}
 6
 7%btn-large {
 8  padding: 0.75rem 1.5rem;
 9  font-size: 1.125rem;
10}
11
12/* Composite button that extends both placeholders */
13.btn--lg {
14  @extend %btn-base;
15  @extend %btn-large;
16  background: #222;
17  color: #fff;
18}
  • นี่คือตัวอย่างปุ่มที่สืบทอด Placeholder สองรายการ คือ 'base' และ 'size'
  • .btn--lg จะสืบทอดทั้ง %btn-base และ %btn-large รวมเลย์เอาต์พื้นฐานกับขนาดที่ใหญ่ขึ้นด้วยกัน

CSS ที่ได้จากการประมวลผล

1.btn--lg {
2  display: inline-block;
3  /* %btn-large overrides the padding from %btn-base */
4  padding: 0.75rem 1.5rem;
5  border-radius: 3px;
6  font-size: 1.125rem;
7  background: #222;
8  color: #fff;
9}

พฤติกรรมของ @extend (กลไกการรวม) และข้อควรระวังเรื่อง 'Selector Explosion'

@extend จะนำ selector ที่ตรงกันทั้งหมดมารวมเข้าด้วยกัน ซึ่งอาจทำให้เกิดการรวมที่ไม่ตั้งใจได้

ตัวอย่างต่อไปนี้แสดงให้เห็นว่าการนำ base class เดียวกันไป extend ในหลายที่จะทำให้ CSS ที่ได้เพิ่มขึ้นอย่างไร

 1/* Many components extend .utility */
 2/* A generic utility class */
 3.utility {
 4  margin-bottom: 1rem;
 5}
 6
 7/* Nested selectors that extend .utility */
 8.header {
 9  @extend .utility;
10  .title {
11    font-weight: bold;
12  }
13}
14
15.footer {
16  @extend .utility;
17  .note {
18    color: #888;
19  }
20}
21
22.article {
23  @extend .utility;
24  .content {
25    line-height: 1.6;
26  }
27}
28
29.sidebar {
30  @extend .utility;
31  .section {
32    padding: 1rem;
33  }
34}
  • เมื่อมีคอมโพเนนต์มากมายสืบทอด .utility selector จะถูกรวมเข้าด้วยกัน และในโครงการขนาดใหญ่ อาจทำให้ CSS มีขนาดใหญ่เกินไปได้

CSS ที่ได้จากการประมวลผล

 1.utility,
 2.header,
 3.footer,
 4.article,
 5.sidebar {
 6  margin-bottom: 1rem;
 7}
 8
 9.header .title {
10  font-weight: bold;
11}
12
13.footer .note {
14  color: #888;
15}
16
17.article .content {
18  line-height: 1.6;
19}
20
21.sidebar .section {
22  padding: 1rem;
23}

@extend กับ .class เทียบกับ Selector สำหรับ Element (Tag) — ลำดับความสำคัญและผลข้างเคียง

@extend สามารถนำไปใช้ได้กับทั้งคลาสและเซเลกเตอร์ขององค์ประกอบ HTML อย่างไรก็ตาม การสืบทอดกับองค์ประกอบจะเพิ่มขอบเขตที่ได้รับผลกระทบ ทำให้เสี่ยงที่กฎจะถูกนำไปใช้ในที่ที่ไม่ตั้งใจ

ด้านล่างคือตัวอย่างการขยาย selector ของ element และผลที่เกิดขึ้น

 1/* Extending an element selector (not recommended) */
 2h1 {
 3  font-size: 2rem;
 4  margin-bottom: 0.5rem;
 5}
 6
 7/* If you extend h1, the resulting selector will include your class with h1 */
 8.title {
 9  @extend h1;
10  color: #333;
11}
12
13/* Output becomes:
14h1, .title { font-size: 2rem; margin-bottom: 0.5rem; }
15*/
  • ในตัวอย่างนี้ การสืบทอด selector ของ element h1 จะทำให้ .title ถูกรวมสไตล์เดียวกับ h1
  • แม้จะดูเหมาะสมสำหรับงานขนาดเล็ก แต่เมื่อโปรเจกต์เติบโตขึ้น อาจเกิดเหตุการณ์ที่กฎของ h1 ถูกรวมเข้ากับ .title โดยไม่คาดคิด ส่งผลให้รูปแบบสไตล์มีความซับซ้อนมากขึ้นและบำรุงรักษายากขึ้น ดังนั้น การออกแบบสไตล์โดยเน้นไปที่คลาสและ Placeholder จะช่วยให้บำรุงรักษาได้ง่ายกว่า

CSS ที่ได้จากการประมวลผล

1h1,
2.title {
3  font-size: 2rem;
4  margin-bottom: 0.5rem;
5}
6
7.title {
8  color: #333;
9}

กรณีการใช้งาน @extend และ !optional

หากคุณระบุ !optional ร่วมกับ @extend จะสามารถป้องกันข้อผิดพลาดเมื่อเป้าหมายของการสืบทอดไม่มีอยู่จริง ฟีเจอร์นี้มีประโยชน์อย่างยิ่งในกรณีที่เขียนโค้ดแบบไลบรารี หรือกรณีที่มีการกำหนด Placeholder แบบมีเงื่อนไข

ต่อไปนี้คือตัวอย่างของการพยายามสืบทอดคลาสที่อาจไม่มีอยู่ โดยใช้ !optional เพื่อความปลอดภัย

1/* Try to extend a class that might not exist */
2.component {
3  @extend .maybe-existing !optional;
4  padding: 1rem;
5}
  • หาก .maybe-existing ไม่มีอยู่ จะไม่มีอะไรเกิดขึ้นและคำสั่งจะถูกข้ามไป สามารถใช้วิธีนี้เมื่อคุณต้องการขยายอย่างปลอดภัย

CSS ที่ได้จากการประมวลผล

1.component {
2  padding: 1rem;
3}

เปรียบเทียบ @extend กับ Mixin (@mixin / @include)

@extend และ @mixin บางครั้งมีวัตถุประสงค์คล้ายกัน แต่ผลลัพธ์และกรณีการใช้งานต่างกัน

  • @extend

    • CSS ที่สร้างจะลดความซ้ำซ้อนด้วยการรวม selector
    • เนื่องจาก selector จะถูกรวมกันหลังจากสร้าง CSS อาจเกิดการจับคู่อย่างไม่ตั้งใจได้
    • ไม่สามารถส่งพารามิเตอร์ได้ (แต่อาจทดแทนได้ด้วยการรวม placeholder)
  • @mixin / @include

    • การเรียกแต่ละครั้งทำให้เกิดสไตล์ซ้ำกัน (ส่งผลให้ output ซ้ำซ้อน)
    • สามารถส่งพารามิเตอร์และใส่โค้ดลอจิก เช่น เงื่อนไขหรือ loop ได้
    • ผลลัพธ์ทำนายง่ายขึ้น แต่ขนาดไฟล์จะใหญ่ขึ้น

ด้านล่างคือการเปรียบเทียบการใช้ @mixin และ @extend เพื่อทำปุ่มเดียวกัน

 1/* Mixin approach */
 2@mixin btn-styles($bg, $color) {
 3  display: inline-block;
 4  padding: 0.5rem 1rem;
 5  background: $bg;
 6  color: $color;
 7  border-radius: 4px;
 8}
 9
10/* Use mixin */
11.btn {
12  @include btn-styles(white, #333);
13}
14
15.btn--primary {
16  @include btn-styles(#007bff, white);
17}
18
19/* Extend approach (shared placeholder) */
20%btn-base {
21  display: inline-block;
22  padding: 0.5rem 1rem;
23  border-radius: 4px;
24}
25
26.btn2 {
27  @extend %btn-base;
28  background: white;
29  color: #333;
30}
31
32.btn2--primary {
33  @extend %btn-base;
34  background: #007bff;
35  color: white;
36}
  • @mixin ให้ความยืดหยุ่นในการใส่สไตล์ ส่วน @extend จะรวมผลลัพธ์อย่างมีประสิทธิภาพ คุณสามารถเลือกใช้งานแต่ละอย่างตามความเหมาะสม

CSS ที่ได้จากการประมวลผล

ผลลัพธ์ที่ได้จาก @mixin
 1.btn {
 2  display: inline-block;
 3  padding: 0.5rem 1rem;
 4  background: white;
 5  color: #333;
 6  border-radius: 4px;
 7}
 8
 9.btn--primary {
10  display: inline-block;
11  padding: 0.5rem 1rem;
12  background: #007bff;
13  color: white;
14  border-radius: 4px;
15}
ผลลัพธ์ที่ได้จาก @extend
 1.btn2,
 2.btn2--primary {
 3  display: inline-block;
 4  padding: 0.5rem 1rem;
 5  border-radius: 4px;
 6}
 7
 8.btn2 {
 9  background: white;
10  color: #333;
11}
12
13.btn2--primary {
14  background: #007bff;
15  color: white;
16}

แนวทางปฏิบัติที่แนะนำ

การสืบทอด (Inheritance) ใน SASS เป็นฟีเจอร์ทรงพลังที่ช่วยเพิ่มความสามารถในการนำสไตล์มาใช้ซ้ำ อย่างไรก็ตาม หากใช้ไม่ถูกต้อง การรวมสไตล์จะซับซ้อนและบำรุงรักษายากขึ้น ด้านล่างนี้คือจุดสำคัญในการใช้ฟีเจอร์ inheritance อย่างปลอดภัยและมีประสิทธิภาพ

  • ควรใช้ Placeholder สำหรับสไตล์ส่วนประกอบพื้นฐานทั่วไป เช่น โครงสร้างและเลย์เอาต์ นอกจากนี้ หากต้องการพารามิเตอร์ที่เปลี่ยนแปลงได้ สามารถใช้ @mixin เพิ่มเติม
  • ควรหลีกเลี่ยงการสืบทอดองค์ประกอบ HTML โดยตรง เช่น h1 อาจเกิดการรวมเซเลกเตอร์ที่ไม่ตั้งใจ ส่งผลให้เกิด CSS ที่ไม่คาดคิดได้
  • การใช้หลักการตั้งชื่อเช่น BEM หรือการใช้ prefix ที่ชัดเจน จะช่วยให้รู้ว่า placeholder นั้นใช้เพื่ออะไรและเพิ่มความปลอดภัย
  • การใช้ @extend ภายในไฟล์เดียวกันจะปลอดภัยกว่า โดยเฉพาะในโปรเจกต์ขนาดใหญ่ ควรออกแบบ inheritance ให้จำกัดอยู่ภายในคอมโพเนนต์แต่ละตัว เพื่อให้ง่ายต่อการติดตามความสัมพันธ์ของการสืบทอด

สรุป

ฟีเจอร์ @extend ใน SASS ช่วยให้สามารถนำสไตล์ทั่วไปมาใช้ซ้ำได้อย่างมีประสิทธิภาพและทำให้ดีไซน์มีความสม่ำเสมอ อย่างไรก็ตาม เนื่องจากการรวมเซเลกเตอร์สามารถซับซ้อนได้ง่าย จึงควรใช้งานฟีเจอร์นี้อย่างระมัดระวังและในขอบเขตที่จำกัด โดยการจัดกลุ่มสไตล์ที่ใช้ร่วมกันด้วย Placeholder Selector (%placeholder) และใช้ @mixin สำหรับส่วนที่ต้องการพารามิเตอร์แบบไดนามิก จะช่วยให้การออกแบบมีความเรียบง่ายและบำรุงรักษาได้ง่าย

คุณสามารถติดตามบทความข้างต้นโดยใช้ Visual Studio Code บนช่อง YouTube ของเรา กรุณาตรวจสอบช่อง YouTube ด้วย

YouTube Video