::part และ exportparts
Shadow DOM ปิดกั้นไม่ให้ CSS ภายนอก select เข้าไปจัดสไตล์ internals ได้ตามปกติ นั่นดีต่อ encapsulation แต่บางครั้งผู้เขียน component ก็อยากเปิดจุดให้ผู้ใช้ theme ได้แบบควบคุมได้ — part attribute คือกลไกที่ให้ทำสิ่งนั้น
attribute part="name" ทำเครื่องหมาย element ภายใน shadow root ให้เป็น styling hook ที่ตั้งชื่อไว้ จากนั้น CSS ของหน้าจะกำหนดเป้าหมายไปที่ element นั้นด้วย pseudo-element ::part(name) บน host ได้ ทำให้ข้ามขอบเขต shadow boundary ได้โดยไม่สูญเสียการ encapsulation ของส่วนอื่น ๆ ผู้เขียน component เป็นผู้ควบคุมว่า internals ใดบ้างที่จะถูกเปิดเผย
<theme-card></theme-card>CSS ของหน้าใช้ ::part() เพื่อจัดสไตล์ internals ที่ถูกเปิดเผย:
theme-card::part(title) { color: #7c3aed; font-size: 1.2rem; font-weight: bold; }theme-card::part(body) { color: #374151; font-size: 0.95rem; margin-top: 0.5rem; }shadow stylesheet ของ component เอง ไม่ควร มีสไตล์ part เหล่านี้ — ให้กำหนดเพียง rule เชิงโครงสร้างหรือเลย์เอาต์เริ่มต้นเท่านั้น rule ::part() อยู่ใน CSS ของหน้าทั้งหมด (หรือใน prop css ของ LivePreview) และ rule เหล่านั้นชนะเพราะเป็นการ opt-in อย่างชัดเจน
exportparts
หัวข้อที่มีชื่อว่า “exportparts”เมื่อ component ประกอบ custom element อื่นไว้ภายใน parts ของ children ใน shadow DOM ของตัวเองจะไม่ปรากฏให้หน้าเห็นโดยอัตโนมัติ attribute exportparts ส่งออก inner parts ใหม่ภายใต้ชื่อใหม่:
<outer-card exportparts="label: card-label"></outer-card>ตอนนี้ CSS ของหน้าสามารถเขียน outer-card::part(card-label) เพื่อเข้าถึง part label ของ component ภายในได้โดยไม่จำเป็นต้องรู้ชื่อ tag ของ element ภายใน
prop css ด้านล่างถูก inject เป็นบล็อก <style> ในหน้า ภายนอก shadow root ของ component — ตรงกับที่ rule ::part() ควรอยู่
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
part + ::part() | เป็น styling API แบบ opt-in ที่ชัดเจน — เฉพาะ element ที่ทำเครื่องหมายไว้เท่านั้นที่จัดสไตล์จากภายนอกได้ รักษา encapsulation ของส่วนที่เหลือ | ชื่อ part กลายเป็น public contract ที่ต้อง maintain เหมือน API — เปลี่ยนชื่อหรือลบ part ทีหลังคือ breaking change สำหรับผู้ใช้ที่พึ่งพา ::part() อยู่ |
exportparts | ส่งออก part ของ component ที่ถูกประกอบซ้อนกันได้ โดยไม่ต้อง expose shadow tree ทั้งหมดของ component ภายใน | เพิ่มชั้นของ mapping ที่ต้อง sync ด้วยมือ หาก inner component เปลี่ยนชื่อ part เดิม exportparts ของ outer component จะพังตาม |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- มองว่า part เป็นแค่รายละเอียดภายใน — เปลี่ยนชื่อหรือลบ
part="name"โดยไม่คิดว่าเป็น public API เวอร์ชันหนึ่งที่ CSS ภายนอกพึ่งพาอยู่ ทำให้::part()ของผู้ใช้พังแบบไม่มีการแจ้งเตือนใด ๆ - ลืมใช้
exportpartsเมื่อ compose component — part ของ custom element ที่ซ้อนอยู่ภายในจะไม่ถูกส่งออกให้ host ภายนอกเห็นโดยอัตโนมัติ ต้องประกาศexportparts="inner: outer-name"อย่างชัดเจนเสมอ - ใส่
partattribute ผิดตำแหน่ง —partต้องอยู่บน element ภายใน shadow root เท่านั้น จะใช้::part()อ้างถึงตัว host เองไม่ได้ ให้ใช้:host/:host()แทนสำหรับสไตล์ของ host
💡 ตัวอย่างจากของจริง
Adobe Spectrum (Spectrum Web Components) — ใช้
::partอย่างกว้างขวางเป็น styling API สำหรับ theme internals ของทุก component พร้อมเอกสารกำกับชื่อ part แต่ละตัวไว้อย่างเป็นทางการ เหมือน public API จริง ๆShoelace — เอกสาร part names ทั้งหมดของแต่ละ component ไว้ในหน้า docs และถือว่าการเปลี่ยนชื่อ part ในเวอร์ชันถัดไปคือ breaking change