จากคอมโพเนนต์เว็บไปยังองค์ประกอบ Lit

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 สนามเด็กเล่นที่มีไฟสว่าง

แถบแท็บตัวเลือกไฟล์มีป้ายกำกับส่วนที่ 1 ส่วนการแก้ไขโค้ดเป็นส่วนที่ 2 การแสดงตัวอย่างเอาต์พุตเป็นส่วนที่ 3 และปุ่มแสดงตัวอย่างโหลดซ้ำเป็นส่วนที่ 4

ภาพหน้าจอ UI ของ Litground ที่ไฮไลต์ส่วนต่างๆ ที่คุณจะใช้ใน Codelab นี้

  1. ตัวเลือกไฟล์ สังเกตปุ่มบวก...
  2. เครื่องมือแก้ไขไฟล์
  3. ตัวอย่างโค้ด
  4. ปุ่มโหลดซ้ำ
  5. ปุ่มดาวน์โหลด

การตั้งค่า VS Code (ขั้นสูง)

ประโยชน์ที่ได้รับจากการตั้งค่า VS Code มีดังนี้

  • การตรวจสอบประเภทเทมเพลต
  • เทมเพลตอัจฉริยะและ การเติมข้อความอัตโนมัติ

หากคุณมีโค้ด NPM, VS (ที่มีปลั๊กอินปลั๊กอินไฟ) ติดตั้งไว้อยู่แล้ว และทราบวิธีใช้สภาพแวดล้อมดังกล่าว คุณเพียงดาวน์โหลดและเริ่มต้นโปรเจ็กต์เหล่านี้โดยทำตามขั้นตอนต่อไปนี้

  • กดปุ่มดาวน์โหลด
  • แตกเนื้อหาของไฟล์ tar ลงในไดเรกทอรี
  • ติดตั้งเซิร์ฟเวอร์ dev ที่สามารถแก้ปัญหาตัวระบุโมดูลเปล่า (ทีม Lit แนะนำ @web/dev-server)
  • เรียกใช้เซิร์ฟเวอร์นักพัฒนาซอฟต์แวร์และเปิดเบราว์เซอร์ (หากใช้ @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 ด้วย

แผนผัง Door ในเครื่องมือตรวจสอบ Chrome มี <rating-element> โดยมี a#shadow-root (เปิด) เป็นองค์ประกอบย่อยและ DOM จากก่อนหน้าภายใน Shadroot นั้น

คุณจะเห็นรูทของเงาที่ขยายได้ที่เก็บเนื้อหาไว้ ทุกอย่างภายในรากเงานั้นเรียกว่า 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 เหตุการณ์ไปใช้

รีเฟรชหน้าเว็บ แล้วคุณจะเห็นปุ่มให้คะแนนที่ใช้งานได้ซึ่งควรมีลักษณะแบบนี้เมื่อกดปุ่มโหวตเห็นด้วย

แถบเลื่อนการให้คะแนนแบบว่าชอบและไม่ชอบซึ่งมีค่าเป็น 6 และนิ้วโป้งชี้ขึ้นสีเขียว

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 and Friends Slack - ชุมชนคอมโพเนนต์ของเว็บที่ใหญ่ที่สุด
  • @buildWithLit บน Twitter - บัญชี Twitter ของทีมที่สร้าง Lit
  • Web Components SF - มีตติ้งเกี่ยวกับคอมโพเนนต์ของเว็บในซานฟรานซิสโก