1. บทนำ
อัปเดตล่าสุด 10-08-2021
คอมโพเนนต์ของเว็บ
Web Components คือชุด API ของแพลตฟอร์มเว็บที่ให้คุณสร้างแท็ก HTML ที่ห่อหุ้มแล้วซ้ำได้และกำหนดเองใหม่เพื่อใช้ในหน้าเว็บและเว็บแอป คอมโพเนนต์และวิดเจ็ตที่กำหนดเองที่สร้างตามมาตรฐาน Web Component จะทำงานได้กับเบราว์เซอร์สมัยใหม่และใช้ได้กับไลบรารี JavaScript หรือเฟรมเวิร์กที่ทำงานร่วมกับ HTML
Lit คืออะไร
Lit เป็นไลบรารีที่เรียบง่ายสำหรับสร้างคอมโพเนนต์เว็บที่รวดเร็วและใช้ทรัพยากรน้อย ซึ่งทำงานได้กับทุกเฟรมเวิร์ก หรือไม่ต้องใช้เฟรมเวิร์กเลย Lit ช่วยให้คุณสามารถสร้างคอมโพเนนต์ แอปพลิเคชัน ระบบการออกแบบ และอื่นๆ ที่แชร์ได้
Lit มี API ที่ช่วยให้งานคอมโพเนนต์ของเว็บทั่วไปง่ายขึ้น เช่น การจัดการพร็อพเพอร์ตี้ แอตทริบิวต์ และการแสดงผล
สิ่งที่คุณจะได้เรียนรู้
- คอมโพเนนต์เว็บคืออะไร
- แนวคิดของคอมโพเนนต์เว็บ
- วิธีสร้างคอมโพเนนต์เว็บ
- lit-html และ LitElement คืออะไร
- สิ่งที่ Lit ทำร่วมกับคอมโพเนนต์เว็บ
สิ่งที่คุณจะสร้าง
- คอมโพเนนต์เว็บแบบ vanilla ชอบ / ไม่ชอบ
- คอมโพเนนต์เว็บแบบชอบ / ไม่ชอบ
สิ่งที่คุณต้องมี
- เบราว์เซอร์รุ่นใหม่ที่อัปเดต (Chrome, Safari, Firefox, Chromium Edge) คอมโพเนนต์ของเว็บทำงานได้ในเบราว์เซอร์สมัยใหม่และ Polyfill ทั้งหมดที่ใช้ได้กับ Microsoft Internet Explorer 11 และ Microsoft Edge ที่ไม่ใช่ Chromium
- ความรู้เกี่ยวกับ HTML, CSS, JavaScript และเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome
2. การตั้งค่า สำรวจสนามเด็กเล่น
การเข้าถึงโค้ด
ตลอดทั้ง Codelab จะมีลิงก์ไปยัง Lit Play แบบ "จำลอง" ดังนี้
สนามเด็กเล่นเป็นแซนด์บ็อกซ์โค้ดที่ทำงานในเบราว์เซอร์ได้อย่างเต็มรูปแบบ เครื่องมือนี้สามารถคอมไพล์และเรียกใช้ไฟล์ TypeScript และ JavaScript และสามารถแก้ไขการนำเข้าไปยังโมดูลโหนดได้โดยอัตโนมัติ เช่น
// before
import './my-file.js';
import 'lit';
// after
import './my-file.js';
import 'https://unpkg.com/lit?module';
คุณสามารถทำตามบทแนะนำทั้งหมดใน Lit Play โดยใช้จุดตรวจสอบเหล่านี้เป็นจุดเริ่มต้น หากคุณใช้ VS Code คุณสามารถใช้จุดตรวจเหล่านี้เพื่อดาวน์โหลดโค้ดเริ่มต้นสำหรับขั้นตอนใดๆ และใช้จุดตรวจสอบในการตรวจสอบงานได้
สำรวจ UI สนามเด็กเล่นที่มีไฟสว่าง
ภาพหน้าจอ UI ของ Litground ที่ไฮไลต์ส่วนต่างๆ ที่คุณจะใช้ใน Codelab นี้
- ตัวเลือกไฟล์ สังเกตปุ่มบวก...
- เครื่องมือแก้ไขไฟล์
- ตัวอย่างโค้ด
- ปุ่มโหลดซ้ำ
- ปุ่มดาวน์โหลด
การตั้งค่า VS Code (ขั้นสูง)
ประโยชน์ที่ได้รับจากการตั้งค่า VS Code มีดังนี้
- การตรวจสอบประเภทเทมเพลต
- เทมเพลตอัจฉริยะและ การเติมข้อความอัตโนมัติ
หากคุณมีโค้ด NPM, VS (ที่มีปลั๊กอินปลั๊กอินไฟ) ติดตั้งไว้อยู่แล้ว และทราบวิธีใช้สภาพแวดล้อมดังกล่าว คุณเพียงดาวน์โหลดและเริ่มต้นโปรเจ็กต์เหล่านี้โดยทำตามขั้นตอนต่อไปนี้
- กดปุ่มดาวน์โหลด
- แตกเนื้อหาของไฟล์ tar ลงในไดเรกทอรี
- ติดตั้งเซิร์ฟเวอร์ dev ที่สามารถแก้ปัญหาตัวระบุโมดูลเปล่า (ทีม Lit แนะนำ @web/dev-server)
- นี่คือตัวอย่าง
package.json
- นี่คือตัวอย่าง
- เรียกใช้เซิร์ฟเวอร์นักพัฒนาซอฟต์แวร์และเปิดเบราว์เซอร์ (หากใช้
@web/dev-server
คุณสามารถใช้npx web-dev-server --node-resolve --watch --open
ได้)- หากคุณใช้ตัวอย่าง
package.json
ให้ใช้npm run serve
- หากคุณใช้ตัวอย่าง
3. กำหนดองค์ประกอบที่กำหนดเอง
องค์ประกอบที่กำหนดเอง
คอมโพเนนต์เว็บคือคอลเล็กชัน API ของเว็บแบบเนทีฟ 4 รายการ ปัจจัยต่างๆ มีดังนี้
- โมดูล ES
- องค์ประกอบที่กำหนดเอง
- Shadow DOM
- เทมเพลต HTML
คุณได้ใช้ข้อกำหนดของโมดูล ES แล้ว ซึ่งช่วยให้คุณสร้างโมดูล JavaScript ที่มีการนำเข้าและส่งออกที่โหลดในหน้าด้วย <script type="module">
การกำหนดองค์ประกอบที่กำหนดเอง
ข้อกำหนดขององค์ประกอบที่กำหนดเองช่วยให้ผู้ใช้กำหนดองค์ประกอบ HTML ของตนเองโดยใช้ JavaScript ได้ ชื่อต้องมีเครื่องหมายขีดกลาง (-
) เพื่อแยกความแตกต่างจากองค์ประกอบของเบราว์เซอร์ที่มาพร้อมเครื่อง ล้างไฟล์ index.js
และกำหนดคลาสขององค์ประกอบที่กำหนดเองดังนี้
index.js
class RatingElement extends HTMLElement {}
customElements.define('rating-element', RatingElement);
องค์ประกอบที่กำหนดเองจะกำหนดโดยการเชื่อมโยงคลาสที่ขยาย HTMLElement
ด้วยชื่อแท็กที่มีเครื่องหมายขีดกลาง การเรียก customElements.define
จะทำให้เบราว์เซอร์เชื่อมโยงคลาส RatingElement
กับ tagName ‘rating-element'
ซึ่งหมายความว่าทุกองค์ประกอบในเอกสารที่ใช้ชื่อ <rating-element>
จะเชื่อมโยงกับคลาสนี้
วาง <rating-element>
ในเนื้อหาเอกสารและดูว่าอะไรแสดงผล
index.html
<body>
<rating-element></rating-element>
</body>
เมื่อดูที่ผลลัพธ์ คุณจะเห็นว่าไม่มีอะไรแสดงผล กรณีนี้เป็นเรื่องปกติ เนื่องจากคุณยังไม่ได้บอกเบราว์เซอร์ว่าจะแสดงผล <rating-element>
อย่างไร คุณสามารถยืนยันว่าคำจำกัดความขององค์ประกอบที่กำหนดเองสำเร็จได้โดยเลือก <rating-element>
ในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome ตัวเลือกองค์ประกอบ และในคอนโซลโดยเรียกใช้:
$0.constructor
ซึ่งควรแสดงผลดังนี้
class RatingElement extends HTMLElement {}
วงจรการใช้งานองค์ประกอบที่กำหนดเอง
องค์ประกอบที่กำหนดเองมาพร้อมกับชุดตะขอสำหรับวงจร ปัจจัยต่างๆ มีดังนี้
constructor
connectedCallback
disconnectedCallback
attributeChangedCallback
adoptedCallback
ระบบจะเรียก constructor
เมื่อสร้างองค์ประกอบครั้งแรก เช่น ด้วยการเรียก document.createElement(‘rating-element')
หรือ new RatingElement()
เครื่องมือสร้างเป็นพื้นที่ที่ดีในการตั้งค่าองค์ประกอบ แต่โดยทั่วไปถือว่าเป็นแนวทางปฏิบัติที่ไม่ถูกต้องในการปรับเปลี่ยน DOM ในเครื่องมือสร้างสำหรับองค์ประกอบ "เริ่มต้นระบบ" เหตุผลด้านประสิทธิภาพ
ระบบจะเรียก connectedCallback
เมื่อมีการแนบองค์ประกอบที่กำหนดเองกับ DOM ซึ่งมักเป็นที่ที่เกิดการจัดการ DOM ครั้งแรก
ระบบจะเรียก disconnectedCallback
หลังจากที่นำองค์ประกอบที่กำหนดเองออกจาก DOM
ระบบจะเรียก attributeChangedCallback(attrName, oldValue, newValue)
เมื่อแอตทริบิวต์ที่ผู้ใช้ระบุมีการเปลี่ยนแปลง
ระบบจะเรียก adoptedCallback
เมื่อมีการนำองค์ประกอบที่กำหนดเองจาก documentFragment
อื่นมาใช้ในเอกสารหลักผ่าน adoptNode
เช่น ใน HTMLTemplateElement
แสดงผล DOM
ตอนนี้ ให้กลับไปที่องค์ประกอบที่กำหนดเองและเชื่อมโยง DOM บางส่วนเข้ากับองค์ประกอบดังกล่าว ตั้งค่าเนื้อหาขององค์ประกอบเมื่อมีการแนบกับ DOM ดังนี้
index.js
class RatingElement extends HTMLElement {
constructor() {
super();
this.rating = 0;
}
connectedCallback() {
this.innerHTML = `
<style>
rating-element {
display: inline-flex;
align-items: center;
}
rating-element button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
`;
}
}
customElements.define('rating-element', RatingElement);
ใน constructor
คุณจะจัดเก็บพร็อพเพอร์ตี้ของอินสแตนซ์ที่ชื่อว่า rating
ในองค์ประกอบ ใน connectedCallback
คุณเพิ่มข้อมูลย่อย DOM ไปยัง <rating-element>
เพื่อแสดงการจัดประเภทปัจจุบันพร้อมทั้งปุ่มชอบและไม่ชอบ
4. Shadow DOM
ทำไมจึงต้องใช้ Shadow DOM
ในขั้นตอนก่อนหน้านี้ คุณจะสังเกตเห็นว่าตัวเลือกในแท็กรูปแบบที่คุณแทรกไว้จะเลือกองค์ประกอบการให้คะแนนบนหน้านั้นและปุ่มต่างๆ ด้วย ซึ่งอาจส่งผลให้รูปแบบหลุดออกจากองค์ประกอบและเลือกโหนดอื่นๆ ที่คุณอาจไม่ได้ตั้งใจจะจัดรูปแบบ นอกจากนี้ สไตล์อื่นๆ ที่อยู่นอกองค์ประกอบที่กำหนดเองนี้อาจจัดรูปแบบโหนดภายในองค์ประกอบที่กำหนดเองโดยไม่ได้ตั้งใจ ตัวอย่างเช่น ลองวางแท็กรูปแบบในส่วนหัวของเอกสารหลัก ดังนี้
index.html
<!DOCTYPE html>
<html>
<head>
<script src="./index.js" type="module"></script>
<style>
span {
border: 1px solid red;
}
</style>
</head>
<body>
<rating-element></rating-element>
</body>
</html>
เอาต์พุตของคุณควรมีกรอบสีแดงล้อมรอบระยะเวลาสำหรับการให้คะแนน กรณีนี้เป็นเรื่องที่ไม่สำคัญ แต่การไม่มีการห่อหุ้ม DOM อาจทำให้เกิดปัญหาขนาดใหญ่ขึ้นสำหรับแอปพลิเคชันที่ซับซ้อนมากขึ้น และ Shadow DOM ก็เข้ามาช่วยแก้ปัญหานี้ได้
การแนบรากของเงา
แนบรากเงากับองค์ประกอบและแสดง DOM ภายในรากดังกล่าว ดังนี้
index.js
class RatingElement extends HTMLElement {
constructor() {
super();
this.rating = 0;
}
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
`;
}
}
customElements.define('rating-element', RatingElement);
เมื่อรีเฟรชหน้า คุณจะสังเกตเห็นว่ารูปแบบในเอกสารหลักจะเลือกโหนดภายใน Shadow Root ไม่ได้อีกต่อไป
คุณทำเช่นนี้ได้อย่างไร ใน connectedCallback
คุณเรียกใช้ this.attachShadow
ซึ่งแนบรากของเงากับองค์ประกอบ โหมด open
หมายความว่าเนื้อหาเงานั้นตรวจสอบได้ และทำให้เข้าถึงรากเงาได้ผ่านทาง this.shadowRoot
ด้วย โปรดดูที่คอมโพเนนต์เว็บในเครื่องมือตรวจสอบ Chrome ด้วย
คุณจะเห็นรูทของเงาที่ขยายได้ที่เก็บเนื้อหาไว้ ทุกอย่างภายในรากเงานั้นเรียกว่า Shadow DOM หากเลือกองค์ประกอบการจัดประเภทในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome และเรียกใช้ $0.children
คุณจะสังเกตเห็นว่าไม่แสดงรายการย่อย เนื่องจาก Shadow DOM ไม่ถือว่าเป็นส่วนหนึ่งของแผนผัง DOM เดียวกันกับแผนผังย่อยโดยตรง แต่เป็น Shadow Tree
แสง DOM
การทดสอบ: เพิ่มโหนดเป็นหน่วยย่อยโดยตรงของ <rating-element>
:
index.html
<rating-element>
<div>
This is the light DOM!
</div>
</rating-element>
รีเฟรชหน้าเว็บ แล้วคุณจะเห็นโหนด DOM ใหม่นี้ใน Light DOM ขององค์ประกอบที่กำหนดเองนี้ไม่ได้แสดงบนหน้าเว็บ เนื่องจาก Shadow DOM มีฟีเจอร์ในการควบคุมวิธีฉายโหนด Light DOM ลงใน Shadow DOM ผ่านองค์ประกอบ <slot>
5. เทมเพลต HTML
ทำไมจึงต้องใช้เทมเพลต
การใช้สตริง innerHTML
และสตริงลิเทอรัลเทมเพลตที่ไม่มีการทำความสะอาดข้อมูลอาจทำให้เกิดปัญหาด้านความปลอดภัยกับการแทรกสคริปต์ วิธีการก่อนหน้านี้ได้รวมการใช้ DocumentFragment
แต่ก็มาพร้อมกับปัญหาอื่นๆ เช่น การโหลดรูปภาพและสคริปต์ที่กำลังทำงานอยู่เมื่อมีการกำหนดเทมเพลต ตลอดจนอุปสรรคในการใช้งานซ้ำได้ องค์ประกอบ <template>
จะเข้ามามีบทบาทในส่วนนี้ เทมเพลตจะให้ DOM แบบเฉื่อย เป็นวิธีการที่มีประสิทธิภาพสูงในการโคลนโหนด และเทมเพลตที่ใช้ซ้ำได้
การใช้เทมเพลต
ถัดไป ให้เปลี่ยนคอมโพเนนต์เพื่อใช้เทมเพลต HTML โดยทำดังนี้
index.html
<body>
<template id="rating-element-template">
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating"></span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
</template>
<rating-element>
<div>
This is the light DOM!
</div>
</rating-element>
</body>
คุณได้ย้ายเนื้อหา DOM ไปไว้ในแท็กเทมเพลตใน DOM ของเอกสารหลัก จากนั้นเปลี่ยนโครงสร้างภายในคำอธิบายองค์ประกอบที่กำหนดเอง ดังนี้
index.js
class RatingElement extends HTMLElement {
constructor() {
super();
this.rating = 0;
}
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});
const templateContent = document.getElementById('rating-element-template').content;
const clonedContent = templateContent.cloneNode(true);
shadowRoot.appendChild(clonedContent);
this.shadowRoot.querySelector('.rating').innerText = this.rating;
}
}
customElements.define('rating-element', RatingElement);
หากต้องการใช้องค์ประกอบเทมเพลตนี้ ให้ค้นหาเทมเพลต รับเนื้อหา และโคลนโหนดเหล่านั้นด้วย templateContent.cloneNode
ซึ่งอาร์กิวเมนต์ true
ทำการโคลนอย่างละเอียด จากนั้นจึงเริ่มต้น Doดังกล่าวด้วยข้อมูล
ขอแสดงความยินดี ตอนนี้คุณมีคอมโพเนนต์เว็บแล้ว น่าเสียดายที่ตอนนี้อุปกรณ์ยังไม่ได้ดำเนินการใดๆ ดังนั้นให้เพิ่มฟังก์ชันการทํางานต่างๆ ต่อไป
6. การเพิ่มฟังก์ชัน
การเชื่อมโยงพร็อพเพอร์ตี้
ปัจจุบันวิธีเดียวในการตั้งค่าการจัดประเภทในองค์ประกอบการจัดประเภทคือการสร้างองค์ประกอบ ตั้งค่าพร็อพเพอร์ตี้ rating
ในออบเจ็กต์ แล้ววางลงในหน้า แต่น่าเสียดายที่องค์ประกอบ HTML ที่มีอยู่แต่เนิ่นๆ มักไม่ทํางาน องค์ประกอบ HTML แบบเนทีฟมีแนวโน้มที่จะมีการอัปเดตทั้งการเปลี่ยนแปลงคุณสมบัติและแอตทริบิวต์
ให้องค์ประกอบที่กำหนดเองอัปเดตมุมมองเมื่อพร็อพเพอร์ตี้ rating
เปลี่ยนแปลงด้วยการเพิ่มบรรทัดต่อไปนี้
index.js
constructor() {
super();
this._rating = 0;
}
set rating(value) {
this._rating = value;
if (!this.shadowRoot) {
return;
}
const ratingEl = this.shadowRoot.querySelector('.rating');
if (ratingEl) {
ratingEl.innerText = this._rating;
}
}
get rating() {
return this._rating;
}
คุณเพิ่มตัวตั้งค่าและ Getter สำหรับพร็อพเพอร์ตี้การจัดประเภท จากนั้นอัปเดตข้อความขององค์ประกอบการจัดประเภทหากมี ซึ่งหมายความว่าหากคุณกำหนดพร็อพเพอร์ตี้การให้คะแนนในองค์ประกอบ ข้อมูลพร็อพเพอร์ตี้จะอัปเดต ด้วยการทดสอบสั้นๆ ในคอนโซลเครื่องมือสำหรับนักพัฒนาเว็บ
การเชื่อมโยงแอตทริบิวต์
จากนั้นอัปเดตมุมมองเมื่อแอตทริบิวต์เปลี่ยนแปลง ซึ่งคล้ายกับอินพุตที่อัปเดตมุมมองเมื่อคุณตั้งค่า<input value="newValue">
โชคดีที่วงจรของคอมโพเนนต์เว็บมี attributeChangedCallback
อัปเดตคะแนนด้วยการเพิ่มบรรทัดต่อไปนี้
index.js
static get observedAttributes() {
return ['rating'];
}
attributeChangedCallback(attributeName, oldValue, newValue) {
if (attributeName === 'rating') {
const newRating = Number(newValue);
this.rating = newRating;
}
}
คุณต้องตั้งค่า Getter แบบคงที่สำหรับ RatingElement.observedAttributes which defines the attributes to be observed for changes
เพื่อให้ attributeChangedCallback
ทริกเกอร์ จากนั้นก็ตั้งค่าการจัดประเภทตามประกาศใน DOM ลองใช้เลย
index.html
<rating-element rating="5"></rating-element>
ตอนนี้คะแนนควรจะมีการปรับปรุงอย่างชัดเจนแล้ว!
ฟังก์ชันการทำงานของปุ่ม
ตอนนี้สิ่งที่ขาดไปก็คือฟังก์ชันการทำงานของปุ่ม ลักษณะการทำงานของคอมโพเนนต์นี้ควรอนุญาตให้ผู้ใช้ระบุคะแนนโหวตเพิ่มหรือลดคะแนนเดียว และให้ฟีดแบ็กที่เป็นภาพแก่ผู้ใช้ คุณสามารถใช้วิธีนี้กับ Listener เหตุการณ์และพร็อพเพอร์ตี้สะท้อนบางรายการ แต่ก่อนอื่นให้อัปเดตสไตล์เพื่อแสดงฟีดแบ็กที่เป็นภาพโดยต่อท้ายบรรทัดต่อไปนี้
index.html
<style>
...
:host([vote=up]) .thumb_up {
fill: green;
}
:host([vote=down]) .thumb_down {
fill: red;
}
</style>
ใน Shadow DOM ตัวเลือก :host
จะหมายถึงโหนดหรือองค์ประกอบที่กำหนดเองที่มี Shadow Root แนบอยู่ ในกรณีนี้ หากแอตทริบิวต์ vote
คือ "up"
ปุ่มชอบจะกลายเป็นสีเขียว แต่หาก vote
เป็น "down", then it will turn the thumb-down button red
ต่อไป ให้ใช้ตรรกะสำหรับกรณีนี้โดยการสร้างพร็อพเพอร์ตี้ / แอตทริบิวต์ที่สะท้อนถึง vote
ซึ่งคล้ายกับวิธีที่คุณใช้ rating
เริ่มต้นด้วยตัวตั้งค่าพร็อพเพอร์ตี้และ Getter ดังนี้
index.js
constructor() {
super();
this._rating = 0;
this._vote = null;
}
set vote(newValue) {
const oldValue = this._vote;
if (newValue === oldValue) {
return;
}
if (newValue === 'up') {
if (oldValue === 'down') {
this.rating += 2;
} else {
this.rating += 1;
}
} else if (newValue === 'down') {
if (oldValue === 'up') {
this.rating -= 2;
} else {
this.rating -= 1;
}
}
this._vote = newValue;
this.setAttribute('vote', newValue);
}
get vote() {
return this._vote;
}
คุณเริ่มต้นพร็อพเพอร์ตี้อินสแตนซ์ _vote
ด้วย null
ใน constructor
และในตัวตั้งค่า คุณตรวจสอบว่าค่าใหม่ต่างออกไปหรือไม่ ในกรณีนี้ คุณจะปรับคะแนนให้เหมาะสมได้ และที่สำคัญคือต้องแสดงแอตทริบิวต์ vote
กลับไปยังผู้จัดด้วย this.setAttribute
จากนั้น ให้ตั้งค่าการเชื่อมโยงแอตทริบิวต์ดังนี้
index.js
static get observedAttributes() {
return ['rating', 'vote'];
}
attributeChangedCallback(attributeName, oldValue, newValue) {
if (attributeName === 'rating') {
const newRating = Number(newValue);
this.rating = newRating;
} else if (attributeName === 'vote') {
this.vote = newValue;
}
}
ซึ่งเป็นกระบวนการเดียวกับที่คุณทำกับการเชื่อมโยงแอตทริบิวต์ rating
คุณเพิ่ม vote
ลงใน observedAttributes
และคุณตั้งค่าพร็อพเพอร์ตี้ vote
ใน attributeChangedCallback
และสุดท้ายคือเพิ่ม Listener กิจกรรมการคลิกบางรายการเพื่อให้การทำงานของปุ่มต่างๆ!
index.js
constructor() {
super();
this._rating = 0;
this._vote = null;
this._boundOnUpClick = this._onUpClick.bind(this);
this._boundOnDownClick = this._onDownClick.bind(this);
}
connectedCallback() {
...
this.shadowRoot.querySelector('.thumb_up')
.addEventListener('click', this._boundOnUpClick);
this.shadowRoot.querySelector('.thumb_down')
.addEventListener('click', this._boundOnDownClick);
}
disconnectedCallback() {
this.shadowRoot.querySelector('.thumb_up')
.removeEventListener('click', this._boundOnUpClick);
this.shadowRoot.querySelector('.thumb_down')
.removeEventListener('click', this._boundOnDownClick);
}
_onUpClick() {
this.vote = 'up';
}
_onDownClick() {
this.vote = 'down';
}
ใน constructor
คุณจะเชื่อมโยง Click Listener บางตัวเข้ากับองค์ประกอบและเก็บข้อมูลอ้างอิงไว้ใกล้ๆ ใน connectedCallback
คุณฟังกิจกรรมการคลิกบนปุ่ม ใน disconnectedCallback
คุณล้าง Listener เหล่านี้ และใน Listener การคลิก คุณต้องตั้งค่า vote
อย่างเหมาะสม
ขอแสดงความยินดี ขณะนี้คุณมีคอมโพเนนต์เว็บที่มีฟีเจอร์ครบถ้วนแล้ว ลองคลิกปุ่มบางปุ่มดูสิ ปัญหาตอนนี้คือไฟล์ JS ของฉันมีขนาดถึง 96 บรรทัด ไฟล์ HTML ของฉัน 43 บรรทัด และโค้ดมีรายละเอียดมากและจำเป็นสำหรับคอมโพเนนต์ง่ายๆ ดังกล่าว นี่คือจุดที่โครงการ Lit ของ Google จะเข้ามาช่วยแก้ปัญหา
7. Lit-html
จุดตรวจสอบโค้ด
ทำไมต้องใช้ lit-html
ประการแรกและสำคัญที่สุดคือ แท็ก <template>
มีประโยชน์และมีประสิทธิภาพ แต่แท็กไม่ได้รับการรวมอยู่ในตรรกะของคอมโพเนนต์ จึงทำให้เผยแพร่เทมเพลตด้วยตรรกะที่เหลือได้ยาก นอกจากนี้ วิธีการนำองค์ประกอบเทมเพลตไปใช้โดยพื้นฐานแล้วคือการใช้โค้ดที่จำเป็น ซึ่งมักจะทำให้เกิดโค้ดที่อ่านได้น้อยกว่าเมื่อเทียบกับรูปแบบการเขียนโค้ดแบบประกาศสิทธิ์
นี่คือที่ที่ lit-html จะช่วยคุณได้! Lit html คือระบบการแสดงผลของ Lit ที่ช่วยให้คุณสามารถเขียนเทมเพลต HTML ใน JavaScript แล้วแสดงผลและแสดงผลเทมเพลตเหล่านั้นอีกครั้งพร้อมกับข้อมูลเพื่อสร้างและอัปเดต DOM ได้อย่างมีประสิทธิภาพ ไลบรารีนี้คล้ายกับไลบรารี JSX และ VDOM ยอดนิยม แต่จะทำงานในเบราว์เซอร์ตั้งแต่แรก และมีประสิทธิภาพสูงกว่าในหลายๆ กรณี
การใช้ Lit HTML
ขั้นตอนถัดไป ให้ย้ายข้อมูลคอมโพเนนต์เว็บแบบเนทีฟ rating-element
เพื่อใช้เทมเพลต Lit ที่ใช้ อัลกอริทึมเทมเพลตที่ติดแท็ก ซึ่งเป็นฟังก์ชันที่ใช้สตริงเทมเพลตเป็นอาร์กิวเมนต์ที่มีไวยากรณ์พิเศษ จากนั้น Lit จะใช้องค์ประกอบเทมเพลตขั้นสูงเพื่อให้การแสดงผลอย่างรวดเร็ว รวมถึงมอบฟีเจอร์ทำความสะอาดบางอย่างเพื่อความปลอดภัย เริ่มต้นโดยการย้ายข้อมูล <template>
ใน index.html
ไปยังเทมเพลต Lit โดยเพิ่มเมธอด render()
ลงในเว็บคอมโพเนนต์ต่อไปนี้
ดัชนี.js
// Dont forget to import from Lit!
import {render, html} from 'lit';
class RatingElement extends HTMLElement {
...
render() {
if (!this.shadowRoot) {
return;
}
const template = html`
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
:host([vote=up]) .thumb_up {
fill: green;
}
:host([vote=down]) .thumb_down {
fill: red;
}
</style>
<button class="thumb_down">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>`;
render(template, this.shadowRoot);
}
}
คุณยังลบเทมเพลตออกจาก index.html
ได้ด้วย ในวิธีแสดงผลนี้ คุณจะกำหนดตัวแปรชื่อ template
และเรียกใช้ฟังก์ชันลิเทอรัลของเทมเพลตที่ติดแท็ก html
คุณจะสังเกตเห็นว่าคุณได้ทำการเชื่อมโยงข้อมูลอย่างง่ายภายในองค์ประกอบ span.rating
โดยใช้ไวยากรณ์การประมาณค่าลิเทอรัลของเทมเพลตของ ${...}
ซึ่งหมายความว่าท้ายที่สุดแล้ว คุณจะไม่จำเป็นต้องอัปเดตโหนดนั้นอย่างชัดแจ้งอีกต่อไป นอกจากนี้ คุณยังเรียกใช้เมธอด render
แบบสว่างซึ่งแสดงผลเทมเพลตแบบพร้อมกันไปยังรากของเงา
การย้ายข้อมูลไปใช้ไวยากรณ์การประกาศ
ตอนนี้คุณกำจัดองค์ประกอบ <template>
แล้ว ให้เปลี่ยนโครงสร้างภายในโค้ดเพื่อเรียกเมธอด render
ที่กำหนดใหม่แทน คุณเริ่มต้นได้โดยใช้การเชื่อมโยง Listener เหตุการณ์ของ lit เพื่อล้างโค้ด Listener ต่อไปนี้
index.js
<button
class="thumb_down"
@click=${() => {this.vote = 'down'}}>
...
<button
class="thumb_up"
@click=${() => {this.vote = 'up'}}>
เทมเพลต Lit สามารถเพิ่ม Listener เหตุการณ์ลงในโหนดที่มีไวยากรณ์การเชื่อมโยง @EVENT_NAME ซึ่งในกรณีนี้คุณจะอัปเดตพร็อพเพอร์ตี้ vote
ทุกครั้งที่มีการคลิกปุ่มเหล่านี้
จากนั้น ล้างโค้ดการเริ่มต้น Listener เหตุการณ์ใน constructor
, connectedCallback
และ disconnectedCallback
:
ดัชนี.js
constructor() {
super();
this._rating = 0;
this._vote = null;
}
connectedCallback() {
this.attachShadow({mode: 'open'});
this.render();
}
// remove disonnectedCallback and _onUpClick and _onDownClick
คุณสามารถนำตรรกะ Click Listener ออกจาก Callback ทั้ง 3 รายการ หรือแม้แต่นำ disconnectedCallback
ออกไปเลยก็ได้ นอกจากนี้ยังนำโค้ดการเริ่มต้น DOM ทั้งหมดออกจาก connectedCallback
ได้ด้วย ซึ่งจะทำให้โค้ดดูสวยขึ้นมาก ซึ่งหมายความว่าคุณสามารถกำจัดเมธอด Listener ที่ใช้ _onUpClick
และ _onDownClick
ได้ด้วย
สุดท้าย ให้อัปเดตตัวตั้งค่าพร็อพเพอร์ตี้เพื่อใช้เมธอด render
เพื่อให้ DOM สามารถอัปเดตเมื่อพร็อพเพอร์ตี้หรือแอตทริบิวต์มีการเปลี่ยนแปลง ดังนี้
index.js
set rating(value) {
this._rating = value;
this.render();
}
...
set vote(newValue) {
const oldValue = this._vote;
if (newValue === oldValue) {
return;
}
if (newValue === 'up') {
if (oldValue === 'down') {
this.rating += 2;
} else {
this.rating += 1;
}
} else if (newValue === 'down') {
if (oldValue === 'up') {
this.rating -= 2;
} else {
this.rating -= 1;
}
}
this._vote = newValue;
this.setAttribute('vote', newValue);
// add render method
this.render();
}
ในจุดนี้ คุณสามารถนำตรรกะการอัปเดต DOM ออกจากตัวตั้งค่า rating
และเพิ่มการเรียกไปยัง render
จากตัวตั้งค่า vote
ได้แล้ว ตอนนี้เทมเพลตจะอ่านได้ง่ายขึ้น เนื่องจากตอนนี้คุณจะเห็นตําแหน่งที่นําการเชื่อมโยงและ Listener เหตุการณ์ไปใช้
รีเฟรชหน้าเว็บ แล้วคุณจะเห็นปุ่มให้คะแนนที่ใช้งานได้ซึ่งควรมีลักษณะแบบนี้เมื่อกดปุ่มโหวตเห็นด้วย
8. LitElement
เหตุใดจึงควรใช้ LitElement
ปัญหาบางอย่างยังคงปรากฏในโค้ด ประการแรก หากคุณเปลี่ยนพร็อพเพอร์ตี้หรือแอตทริบิวต์ vote
อาจเป็นการเปลี่ยนพร็อพเพอร์ตี้ rating
ซึ่งจะส่งผลให้การเรียก render
เกิดขึ้น 2 ครั้ง แม้ว่าการเรียกการแสดงผลซ้ำๆ นั้นจะไม่โอเคและมีประสิทธิผล แต่ JavaScript VM ยังคงใช้เวลาในการเรียกใช้ฟังก์ชันนั้น 2 ครั้งแบบพร้อมกัน ประการที่ 2 การสร้างพร็อพเพอร์ตี้และแอตทริบิวต์ใหม่เป็นเรื่องน่าเบื่อเพราะต้องใช้โค้ดสำเร็จรูปจำนวนมาก นี่คือจุดที่ LitElement
จะมาช่วยคุณ
LitElement
เป็นคลาสพื้นฐานของ Lit สำหรับสร้างคอมโพเนนต์เว็บที่รวดเร็วและน้ำหนักเบา ซึ่งนำไปใช้ในเฟรมเวิร์กและสภาพแวดล้อมได้ ต่อไปลองดูว่า LitElement
จะทำอะไรให้เราได้บ้างใน rating-element
โดยการเปลี่ยนการนำไปใช้งาน
การใช้ LitElement
เริ่มต้นด้วยการนำเข้าและคลาสย่อยของคลาสพื้นฐาน LitElement
จากแพ็กเกจ lit
index.js
import {LitElement, html, css} from 'lit';
class RatingElement extends LitElement {
// remove connectedCallback()
...
คุณนำเข้า LitElement
ซึ่งเป็นคลาสฐานใหม่สำหรับ rating-element
จากนั้นให้นำเข้า html
และสุดท้ายเป็น css
ซึ่งจะอนุญาตให้เรากำหนดตัวอักษรของเทมเพลตที่ติดแท็ก CSS สำหรับคณิตศาสตร์ CSS, เทมเพลต และฟีเจอร์อื่นๆ ขั้นสูงได้
ถัดไป ให้ย้ายรูปแบบจากวิธีการแสดงผลไปยังสไตล์ชีตแบบคงที่ของ Lit ดังนี้
index.js
class RatingElement extends LitElement {
static get styles() {
return css`
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
:host([vote=up]) .thumb_up {
fill: green;
}
:host([vote=down]) .thumb_down {
fill: red;
}
`;
}
...
นี่คือที่ที่สไตล์ส่วนใหญ่อาศัยอยู่ในลิต Lit จะนำสไตล์เหล่านี้มาใช้และใช้ฟีเจอร์ของเบราว์เซอร์ เช่น สไตล์ชีตที่สามารถสร้างได้ เพื่อมอบเวลาในการแสดงผลที่เร็วขึ้น รวมถึงส่งต่อผ่าน Polyfill ของ Web Components ในเบราว์เซอร์รุ่นเก่าหากจำเป็น
วงจร
Lit นำเสนอชุดเมธอด Callback สำหรับวงจรของการแสดงผลนอกเหนือจากการเรียกกลับของคอมโพเนนต์เว็บแบบเนทีฟ Callback เหล่านี้จะทำงานเมื่อมีการเปลี่ยนแปลงพร็อพเพอร์ตี้ Lit ที่ประกาศ
หากต้องการใช้ฟีเจอร์นี้ คุณต้องประกาศแบบคงที่ว่าจะให้พร็อพเพอร์ตี้ใดทริกเกอร์วงจรการแสดงผล
ดัชนี.js
static get properties() {
return {
rating: {
type: Number,
},
vote: {
type: String,
reflect: true,
}
};
}
// remove observedAttributes() and attributeChangedCallback()
// remove set rating() get rating()
ในส่วนนี้ ให้คุณระบุว่า rating
และ vote
จะทริกเกอร์วงจรการแสดงผล LitElement รวมถึงกำหนดประเภทที่จะใช้ในการแปลงแอตทริบิวต์สตริงเป็นพร็อพเพอร์ตี้
<user-profile .name=${this.user.name} .age=${this.user.age}>
${this.user.family.map(member => html`
<family-member
.name=${member.name}
.relation=${member.relation}>
</family-member>`)}
</user-profile>
นอกจากนี้ แฟล็ก reflect
ในพร็อพเพอร์ตี้ vote
จะอัปเดตแอตทริบิวต์ vote
ขององค์ประกอบโฮสต์ที่คุณทริกเกอร์ด้วยตนเองในตัวตั้งค่า vote
โดยอัตโนมัติ
เมื่อมีบล็อกพร็อพเพอร์ตี้แบบคงที่แล้ว คุณสามารถนำตรรกะการอัปเดตแอตทริบิวต์และการแสดงผลพร็อพเพอร์ตี้ทั้งหมดออกได้ ซึ่งหมายความว่าคุณสามารถนำวิธีการต่อไปนี้ออก
connectedCallback
observedAttributes
attributeChangedCallback
rating
(ตัวตั้งค่าและเกเตอร์)vote
(ตัวตั้งค่าและ Getter แต่เก็บตรรกะการเปลี่ยนแปลงจากตัวตั้งค่า)
สิ่งที่คุณเก็บไว้คือ constructor
รวมถึงการเพิ่มเมธอดวงจร willUpdate
ใหม่:
index.js
constructor() {
super();
this.rating = 0;
this.vote = null;
}
willUpdate(changedProps) {
if (changedProps.has('vote')) {
const newValue = this.vote;
const oldValue = changedProps.get('vote');
if (newValue === 'up') {
if (oldValue === 'down') {
this.rating += 2;
} else {
this.rating += 1;
}
} else if (newValue === 'down') {
if (oldValue === 'up') {
this.rating -= 2;
} else {
this.rating -= 1;
}
}
}
}
// remove set vote() and get vote()
ในส่วนนี้ ให้คุณเริ่มต้น rating
และ vote
แล้วย้ายตรรกะตัวตั้งค่า vote
ไปยังเมธอดวงจร willUpdate
ระบบจะเรียกใช้เมธอด willUpdate
ก่อน render
เมื่อใดก็ตามที่พร็อพเพอร์ตี้การอัปเดตมีการเปลี่ยนแปลง เนื่องจากพร็อพเพอร์ตี้กลุ่ม LitElement เปลี่ยนแปลงและทำให้การแสดงผลเป็นแบบไม่พร้อมกัน การเปลี่ยนแปลงพร็อพเพอร์ตี้เชิงรับ (เช่น this.rating
) ใน willUpdate
จะไม่เรียกใช้การเรียกใช้วงจร render
ที่ไม่จำเป็น
สุดท้าย render
เป็นเมธอดวงจรของ LitElement ซึ่งทำให้เราแสดงเทมเพลต Lit ได้
index.js
render() {
return html`
<button
class="thumb_down"
@click=${() => {this.vote = 'down'}}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button
class="thumb_up"
@click=${() => {this.vote = 'up'}}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>`;
}
คุณไม่จำเป็นต้องตรวจสอบรากที่เงาอีกต่อไป และไม่จำเป็นต้องเรียกใช้ฟังก์ชัน render
ที่นำเข้าก่อนหน้านี้จากแพ็กเกจ 'lit'
อีกต่อไป
องค์ประกอบควรแสดงผลในตัวอย่างตอนนี้ คลิกได้เลย!
9. ขอแสดงความยินดี
ยินดีด้วย คุณสร้างคอมโพเนนต์ของเว็บจากศูนย์เรียบร้อยแล้ว และได้พัฒนาเป็น LitElement แล้ว!
Lit มีขนาดเล็กมาก (ลดขนาดไฟล์แล้ว < 5kb + gzip) เร็วมาก และเขียนโค้ดสนุกมาก คุณสร้างคอมโพเนนต์ให้เฟรมเวิร์กอื่นๆ ใช้ หรือสร้างแอปเวอร์ชันเต็มด้วยก็ได้
ตอนนี้คุณรู้แล้วว่าคอมโพเนนต์เว็บคืออะไร วิธีสร้าง และ Lit ช่วยให้คุณสร้างคอมโพเนนต์เหล่านั้นได้ง่ายขึ้นได้อย่างไร
จุดตรวจสอบโค้ด
ต้องการตรวจสอบโค้ดเวอร์ชันสุดท้ายเทียบกับโค้ดของเราไหม เปรียบเทียบได้ที่นี่
สิ่งที่ต้องทำต่อไป
ลองดู Codelab อื่นๆ
- Lit for React Developers
- สร้าง Brick Viewer ที่มีองค์ประกอบที่มีแสงส่องสว่าง
- สร้างคอมโพเนนต์เรื่องราวที่มีองค์ประกอบที่มีแสงสว่าง
อ่านเพิ่มเติม
- บทแนะนำแบบอินเทอร์แอกทีฟเกี่ยวกับ Lite
- เอกสารวรรณกรรม
- Open Web Components - ชุมชนที่เรียกใช้คำแนะนำและชุมชนการใช้เครื่องมือ
- WebComponents.dev - สร้างคอมโพเนนต์เว็บในเฟรมเวิร์กที่รู้จักทั้งหมด
ชุมชน
- Lit and Friends Slack - ชุมชนคอมโพเนนต์ของเว็บที่ใหญ่ที่สุด
- @buildWithLit บน Twitter - บัญชี Twitter ของทีมที่สร้าง Lit
- Web Components SF - มีตติ้งเกี่ยวกับคอมโพเนนต์ของเว็บในซานฟรานซิสโก