จอกศักดิ์สิทธิ์ของส่วนประกอบที่นำกลับมาใช้ใหม่ได้: องค์ประกอบที่กำหนดเอง, Shadow DOM และ NPM
เผยแพร่แล้ว: 2022-03-10แม้แต่ส่วนประกอบที่เรียบง่ายที่สุด ต้นทุนแรงงานคนก็อาจมีนัยสำคัญ ทีม UX ทำการทดสอบการใช้งาน ผู้มีส่วนได้ส่วนเสียจำนวนมากต้องลงนามในการออกแบบ
นักพัฒนาดำเนินการทดสอบ AB การตรวจสอบการช่วยสำหรับการเข้าถึง การทดสอบหน่วย และการตรวจสอบข้ามเบราว์เซอร์ เมื่อคุณแก้ไขปัญหาได้แล้ว คุณไม่ต้องการที่จะทำซ้ำ ด้วยการสร้างไลบรารีส่วนประกอบที่นำกลับมาใช้ใหม่ได้ (แทนที่จะสร้างทุกอย่างตั้งแต่เริ่มต้น) เราสามารถใช้ความพยายามในอดีตได้อย่างต่อเนื่องและหลีกเลี่ยงการทบทวนความท้าทายด้านการออกแบบและการพัฒนาที่แก้ไขแล้ว
การสร้างคลังแสงของส่วนประกอบมีประโยชน์อย่างยิ่งสำหรับบริษัทต่างๆ เช่น Google ที่เป็นเจ้าของเว็บไซต์จำนวนมากซึ่งมีแบรนด์ร่วมกันทั้งหมด ด้วยการเข้ารหัส UI ของพวกเขาเป็นวิดเจ็ตที่ประกอบได้ บริษัทขนาดใหญ่สามารถทั้งเร่งเวลาในการพัฒนาและบรรลุความสอดคล้องของทั้งการออกแบบภาพและการโต้ตอบกับผู้ใช้ในโครงการต่างๆ มีความสนใจในคู่มือสไตล์และไลบรารีรูปแบบเพิ่มขึ้นในช่วงหลายปีที่ผ่านมา เนื่องจากนักพัฒนาและนักออกแบบหลายคนกระจายอยู่หลายทีม บริษัทขนาดใหญ่จึงพยายามบรรลุความสม่ำเสมอ เราสามารถทำได้ดีกว่าตัวอย่างสีธรรมดา สิ่งที่เราต้องการคือรหัสที่แจกจ่ายได้ง่าย
การแบ่งปันและการใช้รหัสซ้ำ
การคัดลอกและวางโค้ดด้วยตนเองนั้นง่ายดาย การรักษารหัสนั้นให้ทันสมัยอยู่เสมอนั้นเป็นฝันร้ายของการบำรุงรักษา นักพัฒนาหลายคนจึงพึ่งพาตัวจัดการแพ็คเกจเพื่อนำรหัสมาใช้ซ้ำในโครงการต่างๆ แม้จะมีชื่อ แต่ Node Package Manager ก็กลายเป็นแพลตฟอร์มที่ยอดเยี่ยมสำหรับการจัดการแพ็คเกจ ส่วนหน้า ปัจจุบันมีแพ็คเกจมากกว่า 700,000 รายการในการลงทะเบียน NPM และมีการดาวน์โหลดแพ็คเกจ หลายพันล้าน รายการทุกเดือน โฟลเดอร์ใดๆ ที่มีไฟล์ package.json สามารถอัปโหลดไปยัง NPM เป็นแพ็คเกจที่แชร์ได้ แม้ว่า NPM จะเชื่อมโยงกับ JavaScript เป็นหลัก แต่แพ็คเกจสามารถรวม CSS และมาร์กอัปได้ NPM ทำให้ง่ายต่อการใช้ซ้ำ และที่สำคัญคือ อัปเดต โค้ด แทนที่จะต้องแก้ไขรหัสในหลาย ๆ ที่ คุณเปลี่ยนรหัสในแพ็คเกจเท่านั้น
ปัญหามาร์กอัป
Sass และ Javascript พกพาได้ง่ายด้วยการใช้คำสั่งนำเข้า ภาษาของเทมเพลตทำให้ HTML มีความสามารถเหมือนกัน — เทมเพลตสามารถนำเข้าส่วนย่อยของ HTML ในรูปแบบบางส่วนได้ คุณสามารถเขียนมาร์กอัปสำหรับส่วนท้ายของคุณได้ เช่น เพียงครั้งเดียว แล้วรวมไว้ในเทมเพลตอื่น กล่าวได้ว่าภาษาที่ใช้สร้างเทมเพลตนั้นมีมากมายหลายหลากจะเป็นการพูดน้อยไป การผูกตัวเองไว้กับรหัส เพียงอันเดียว เป็นการจำกัดความสามารถในการนำรหัสกลับมาใช้ใหม่ได้อย่างรุนแรง ทางเลือกอื่นคือการคัดลอกและวางมาร์กอัปและใช้ NPM สำหรับสไตล์และจาวาสคริปต์เท่านั้น
นี่คือแนวทางปฏิบัติของ Financial Times ที่มีไลบรารีองค์ประกอบ Origami ในการพูดคุยของเธอ“ Can't You Just Make It More like Bootstrap?” Alice Bartlett สรุปว่า "ไม่มีทางที่ดีที่จะให้ผู้คนรวมเทมเพลตไว้ในโครงการของพวกเขา" เมื่อพูดถึงประสบการณ์ของเขาในการบำรุงรักษาไลบรารีส่วนประกอบที่ Lonely Planet เอียน เฟเธอร์ได้ย้ำถึงปัญหาของแนวทางนี้:
“เมื่อพวกเขาคัดลอกโค้ดนั้น พวกเขากำลังตัดเวอร์ชันที่จำเป็นอย่างไม่มีกำหนด เมื่อพวกเขาคัดลอกมาร์กอัปสำหรับองค์ประกอบที่ใช้งานได้ จะมีลิงก์โดยนัยไปยังสแน็ปช็อตของ CSS ณ จุดนั้น หากคุณอัปเดตเทมเพลตหรือปรับโครงสร้าง CSS ใหม่ คุณจะต้องอัปเดตเทมเพลตทุกเวอร์ชันที่กระจายอยู่ทั่วไซต์ของคุณ”
วิธีแก้ปัญหา: Web Components
ส่วนประกอบของเว็บช่วยแก้ปัญหานี้ด้วยการกำหนดมาร์กอัปใน JavaScript ผู้เขียนองค์ประกอบสามารถเปลี่ยนแปลงมาร์กอัป, CSS และ Javascript ได้ฟรี ผู้ใช้ส่วนประกอบสามารถได้รับประโยชน์จากการอัพเกรดเหล่านี้โดยไม่จำเป็นต้องลากอวนผ่านโครงการแก้ไขรหัสด้วยมือ การซิงค์กับการเปลี่ยนแปลงล่าสุดทั่วทั้งโครงการสามารถทำได้ด้วยการ npm update
ผ่านเทอร์มินัล เฉพาะชื่อของส่วนประกอบและ API เท่านั้นที่ต้องสอดคล้องกัน
การติดตั้งส่วนประกอบเว็บนั้นง่ายเพียงแค่พิมพ์ npm install component-name
ลงในเทอร์มินัล Javascript สามารถรวมเข้ากับคำสั่งนำเข้า:
<script type="module"> import './node_modules/component-name/index.js'; </script>
จากนั้นคุณสามารถใช้ส่วนประกอบที่ใดก็ได้ในมาร์กอัปของคุณ นี่คือส่วนประกอบตัวอย่างง่ายๆ ที่คัดลอกข้อความไปยังคลิปบอร์ด
แนวทางที่เน้นองค์ประกอบเป็นศูนย์กลางในการพัฒนาส่วนหน้าได้กลายเป็นที่แพร่หลายโดยเฟรมเวิร์ก React ของ Facebook เมื่อพิจารณาจากความแพร่หลายของกรอบงานในเวิร์กโฟลว์ส่วนหน้าสมัยใหม่ บริษัทจำนวนหนึ่งได้สร้างไลบรารีส่วนประกอบโดยใช้กรอบงานที่พวกเขาเลือกอย่างหลีกเลี่ยงไม่ได้ ส่วนประกอบเหล่านั้นสามารถนำกลับมาใช้ใหม่ได้ภายในกรอบงานนั้น ๆ เท่านั้น
เป็นเรื่องยากสำหรับบริษัทขนาดใหญ่ที่จะมี front-end ที่เหมือนกัน และการทำซ้ำจากเฟรมเวิร์กหนึ่งไปอีกเฟรมหนึ่งไม่ใช่เรื่องแปลก กรอบมาและไป เพื่อให้สามารถใช้ซ้ำได้มากที่สุดในโปรเจ็กต์ต่างๆ เราต้องการส่วนประกอบที่เป็น framework ที่ไม่เชื่อเรื่องพระเจ้า
“ฉันได้สร้างเว็บแอปพลิเคชันโดยใช้: Dojo, Mootools, Prototype, jQuery, Backbone, Thorax และ React ในช่วงหลายปีที่ผ่านมา... ฉันชอบที่จะสามารถนำองค์ประกอบ Dojo นักฆ่าที่ฉันใช้ไปเป็นทาสกับ React ของฉัน แอพของวันนี้”
— Dion Almaer ผู้อำนวยการฝ่ายวิศวกรรม Google
เมื่อเราพูดถึงองค์ประกอบเว็บ เรากำลังพูดถึงการรวมกันขององค์ประกอบที่กำหนดเองกับ Shadow DOM องค์ประกอบที่กำหนดเองและ DOM เงาเป็นส่วนหนึ่งของทั้งข้อกำหนด W3C DOM และ WHATWG DOM Standard ซึ่ง หมายความว่าส่วนประกอบของเว็บเป็นมาตรฐาน ของเว็บ ในที่สุดองค์ประกอบที่กำหนดเองและ DOM เงาได้ รับ การตั้งค่าให้รองรับข้ามเบราว์เซอร์ได้ในปีนี้ ด้วยการใช้ส่วนมาตรฐานของแพลตฟอร์มเว็บแบบเนทีฟ เรามั่นใจว่าคอมโพเนนต์ของเราสามารถอยู่รอดจากวงจรที่เปลี่ยนแปลงอย่างรวดเร็วของการปรับโครงสร้างส่วนหน้าและการปรับโครงสร้างทางสถาปัตยกรรมใหม่ คอมโพเนนต์ของเว็บสามารถใช้กับภาษาเทมเพลตและเฟรมเวิร์กส่วนหน้าใดๆ ก็ได้ โดยสามารถทำงานร่วมกันได้อย่างแท้จริงและทำงานร่วมกันได้ สามารถใช้งานได้ทุกที่ตั้งแต่บล็อก Wordpress ไปจนถึงแอปพลิเคชันหน้าเดียว
การสร้างส่วนประกอบเว็บ
การกำหนดองค์ประกอบที่กำหนดเอง
เป็นไปได้เสมอที่จะสร้างชื่อแท็กและให้เนื้อหาปรากฏบนหน้า
<made-up-tag>Hello World!</made-up-tag>
HTML ได้รับการออกแบบมาให้ทนต่อความผิดพลาด ด้านบนจะแสดงแม้ว่าจะไม่ใช่องค์ประกอบ HTML ที่ถูกต้อง ไม่เคยมีเหตุผลที่ดีในการทำเช่นนี้ การเบี่ยงเบนจากแท็กมาตรฐานเป็นแนวทางที่ไม่ดี อย่างไรก็ตาม โดยการกำหนดแท็กใหม่โดยใช้องค์ประกอบ API ที่กำหนดเอง เราสามารถเพิ่ม HTML ด้วยองค์ประกอบที่ใช้ซ้ำได้ซึ่งมีฟังก์ชันในตัว การสร้างองค์ประกอบที่กำหนดเองนั้นเหมือนกับการสร้างส่วนประกอบใน React — แต่นี่คือการขยาย HTMLElement
class ExpandableBox extends HTMLElement { constructor() { super() } }
การเรียก super()
แบบไม่มีพารามิเตอร์ต้องเป็นคำสั่งแรกในตัวสร้าง ตัวสร้างควรใช้เพื่อตั้งค่าสถานะเริ่มต้นและค่าเริ่มต้น และเพื่อตั้งค่าตัวฟังเหตุการณ์ องค์ประกอบที่กำหนดเองใหม่จะต้องถูกกำหนดด้วยชื่อสำหรับแท็ก HTML และองค์ประกอบที่สอดคล้องกับคลาส:
customElements.define('expandable-box', ExpandableBox)
เป็นข้อตกลงในการใช้ชื่อคลาสเป็นตัวพิมพ์ใหญ่ อย่างไรก็ตาม ไวยากรณ์ของแท็ก HTML เป็นมากกว่าแบบแผน จะเกิดอะไรขึ้นถ้าเบราว์เซอร์ต้องการใช้องค์ประกอบ HTML ใหม่และพวกเขาต้องการเรียกมันว่า expandable-box? เพื่อป้องกันความขัดแย้งในการตั้งชื่อ แท็ก HTML มาตรฐานใหม่จะไม่มีเครื่องหมายขีดกลาง ในทางตรงกันข้าม ชื่อขององค์ประกอบที่กำหนดเอง ต้อง มีเส้นประ
customElements.define('whatever', Whatever) // invalid customElements.define('what-ever', Whatever) // valid
วงจรชีวิตองค์ประกอบที่กำหนดเอง
API นำเสนอ ปฏิกิริยา องค์ประกอบแบบกำหนดเองสี่แบบ — ฟังก์ชันที่สามารถกำหนดได้ภายในคลาสที่จะถูกเรียกโดยอัตโนมัติเพื่อตอบสนองต่อเหตุการณ์บางอย่างในวงจรชีวิตขององค์ประกอบที่กำหนดเอง
ConnectCallback จะทำงานเมื่อมีการเพิ่มองค์ประกอบที่กำหนดเองลงใน DOM
connectedCallback() { console.log("custom element is on the page!") }
ซึ่งรวมถึงการเพิ่มองค์ประกอบด้วย Javascript:
document.body.appendChild(document.createElement("expandable-box")) //“custom element is on the page”
เช่นเดียวกับการรวมองค์ประกอบภายในหน้าด้วยแท็ก HTML:
<expandable-box></expandable-box> // "custom element is on the page"
งานใดๆ ที่เกี่ยวข้องกับการดึงทรัพยากรหรือการแสดงผลควรอยู่ที่นี่
disconnectedCallback จะทำงานเมื่อองค์ประกอบที่กำหนดเองถูกลบออกจาก DOM
disconnectedCallback() { console.log("element has been removed") } document.querySelector("expandable-box").remove() //"element has been removed"
adoptedCallback
จะทำงานเมื่อองค์ประกอบที่กำหนดเองถูกนำมาใช้ในเอกสารใหม่ คุณอาจไม่จำเป็นต้องกังวลเกี่ยวกับเรื่องนี้บ่อยเกินไป
attributeChangedCallback
จะทำงานเมื่อมีการเพิ่ม เปลี่ยนแปลง หรือลบแอตทริบิวต์ สามารถใช้เพื่อฟังการเปลี่ยนแปลงทั้งแอตทริบิวต์ดั้งเดิมที่เป็นมาตรฐาน เช่น disabled หรือ src รวมถึงแอตทริบิวต์ที่กำหนดเองที่เราสร้างขึ้น นี่เป็นหนึ่งในองค์ประกอบที่กำหนดเองที่ทรงพลังที่สุด เนื่องจากช่วยสร้าง API ที่เป็นมิตรกับผู้ใช้
แอตทริบิวต์องค์ประกอบที่กำหนดเอง
มีแอตทริบิวต์ HTML มากมาย เพื่อให้เบราว์เซอร์ไม่ต้องเสียเวลาเรียก attributeChangedCallback
ของเราเมื่อ มี การเปลี่ยนแปลงแอตทริบิวต์ เราจำเป็นต้องจัดเตรียมรายการการเปลี่ยนแปลงแอตทริบิวต์ที่เราต้องการรับฟัง สำหรับตัวอย่างนี้ เราสนใจเพียงรายการเดียวเท่านั้น
static get observedAttributes() { return ['expanded'] }
ดังนั้นตอนนี้ attributeChangedCallback
ของเราจะถูกเรียกเมื่อเราเปลี่ยนค่าของแอตทริบิวต์ที่ขยายบนองค์ประกอบที่กำหนดเองเท่านั้น เนื่องจากเป็นแอตทริบิวต์เดียวที่เราระบุไว้
แอตทริบิวต์ HTML สามารถมีค่าที่สอดคล้องกัน (คิดว่า href, src, alt, ค่า ฯลฯ ) ในขณะที่ค่าอื่น ๆ อาจเป็นจริงหรือเท็จ (เช่น disabled, selected, required ) สำหรับแอตทริบิวต์ที่มีค่าที่สอดคล้องกัน เราจะรวมข้อมูลต่อไปนี้ไว้ในคำจำกัดความคลาสขององค์ประกอบที่กำหนดเอง
get yourCustomAttributeName() { return this.getAttribute('yourCustomAttributeName'); } set yourCustomAttributeName(newValue) { this.setAttribute('yourCustomAttributeName', newValue); }
สำหรับอิลิเมนต์ตัวอย่างของเรา แอ็ตทริบิวต์จะเป็น true หรือ false ดังนั้นการกำหนด getter และ setter จึงแตกต่างกันเล็กน้อย
get expanded() { return this.hasAttribute('expanded') } // the second argument for setAttribute is mandatory, so we'll use an empty string set expanded(val) { if (val) { this.setAttribute('expanded', ''); } else { this.removeAttribute('expanded') } }
เมื่อจัดการกับสำเร็จรูปแล้ว เราก็สามารถใช้ attributeChangedCallback
ได้
attributeChangedCallback(name, oldval, newval) { console.log(`the ${name} attribute has changed from ${oldval} to ${newval}!!`); // do something every time the attribute changes }
ตามเนื้อผ้า การกำหนดค่าองค์ประกอบ Javascript จะเกี่ยวข้องกับการส่งอาร์กิวเมนต์ไปยังฟังก์ชัน init
ด้วยการใช้ attributeChangedCallback
คุณสามารถสร้างองค์ประกอบแบบกำหนดเองที่สามารถกำหนดค่าได้ด้วยมาร์กอัป
สามารถใช้ Shadow DOM และองค์ประกอบที่กำหนดเองแยกกันได้ และคุณอาจพบว่าองค์ประกอบที่กำหนดเองมีประโยชน์ทั้งหมดด้วยตัวมันเอง ต่างจาก shadow DOM พวกเขา สามารถ โพลีฟิล อย่างไรก็ตาม สเปกทั้งสองทำงานร่วมกันได้ดี
การแนบมาร์กอัปและรูปแบบด้วย Shadow DOM
จนถึงตอนนี้ เราได้จัดการกับ พฤติกรรม ขององค์ประกอบที่กำหนดเองแล้ว อย่างไรก็ตาม ในส่วนที่เกี่ยวกับมาร์กอัปและสไตล์ องค์ประกอบที่กำหนดเองของเราเทียบเท่ากับ <span>
ที่ไม่มีสไตล์ ในการสรุป HTML และ CSS ให้เป็นส่วนหนึ่งขององค์ประกอบ เราจำเป็นต้องแนบ DOM เงา ควรทำสิ่งนี้ภายในฟังก์ชันตัวสร้าง
class FancyComponent extends HTMLElement { constructor() { super() var shadowRoot = this.attachShadow({mode: 'open'}) shadowRoot.innerHTML = `<h2>hello world!</h2>` }
ไม่ต้องกังวลกับการทำความเข้าใจความหมายของโหมด — คุณต้องรวมข้อมูลสำเร็จรูป แต่คุณต้องการ open
อยู่เสมอ องค์ประกอบตัวอย่างง่ายๆ นี้จะแสดงข้อความว่า "สวัสดีชาวโลก" เช่นเดียวกับองค์ประกอบ HTML อื่นๆ องค์ประกอบที่กำหนดเองสามารถมีลูกได้ แต่ไม่ใช่ตามค่าเริ่มต้น จนถึงตอนนี้ องค์ประกอบที่กำหนดเองด้านบนที่เราได้กำหนดไว้จะไม่แสดงลูกๆ ไปที่หน้าจอ ในการแสดงเนื้อหาระหว่างแท็ก เราจำเป็นต้องใช้องค์ประกอบ slot
shadowRoot.innerHTML = ` <h2>hello world!</h2> <slot></slot> `
เราสามารถใช้แท็กสไตล์เพื่อใช้ CSS บางส่วนกับองค์ประกอบ
shadowRoot.innerHTML = `<style> p { color: red; } </style> <h2>hello world!</h2> <slot>some default content</slot>`
สไตล์เหล่านี้จะมีผลกับองค์ประกอบเท่านั้น ดังนั้นเราจึงสามารถใช้ตัวเลือกองค์ประกอบได้อย่างอิสระโดยไม่กระทบกับส่วนอื่นของหน้า สิ่งนี้ทำให้การเขียน CSS ง่ายขึ้น ทำให้การตั้งชื่อแบบแผนเช่น BEM ไม่จำเป็น
การเผยแพร่ส่วนประกอบบน NPM
แพ็คเกจ NPM เผยแพร่ผ่านบรรทัดคำสั่ง เปิดหน้าต่างเทอร์มินัลแล้วย้ายไปยังไดเร็กทอรีที่คุณต้องการเปลี่ยนเป็นแพ็คเกจที่ใช้ซ้ำได้ จากนั้นพิมพ์คำสั่งต่อไปนี้ลงในเทอร์มินัล:
- หากโปรเจ็กต์ของคุณยังไม่มี package.json
npm init
จะนำคุณไปสู่ขั้นตอนการสร้าง -
npm adduser
เชื่อมโยงเครื่องของคุณกับบัญชี NPM ของคุณ หากคุณไม่มีบัญชีที่มีอยู่ก่อนแล้ว ระบบจะสร้างบัญชีใหม่ให้คุณ -
npm publish
ถ้าทุกอย่างเป็นไปด้วยดี ตอนนี้คุณมีส่วนประกอบในรีจิสทรี NPM ที่พร้อมสำหรับการติดตั้งและใช้งานในโครงการของคุณเอง — และแชร์กับคนทั้งโลก
API ของเว็บคอมโพเนนต์ไม่สมบูรณ์แบบ ขณะนี้องค์ประกอบที่กำหนดเองไม่สามารถรวมข้อมูลในการส่งแบบฟอร์ม เรื่องราวการเพิ่มประสิทธิภาพแบบก้าวหน้านั้นไม่ค่อยดีนัก การจัดการกับการเข้าถึงได้ไม่ง่ายอย่างที่ควรจะเป็น
แม้ว่าจะประกาศครั้งแรกในปี 2011 แต่การรองรับเบราว์เซอร์ยังไม่เป็นสากล การสนับสนุน Firefox จะครบกำหนดในปลายปีนี้ อย่างไรก็ตาม บางเว็บไซต์ที่มีชื่อเสียง (เช่น Youtube) ก็มีการใช้งานอยู่แล้ว แม้จะมีข้อบกพร่องในปัจจุบัน แต่สำหรับส่วนประกอบที่แชร์ได้ใน ระดับสากล สิ่งเหล่านี้เป็นตัวเลือกเดียว และในอนาคต เราสามารถคาดหวังการเพิ่มเติมที่น่าตื่นเต้นสำหรับสิ่งที่พวกเขานำเสนอ