1. บทนำ
Lit คืออะไร
Lit เป็นไลบรารีที่เรียบง่ายสำหรับสร้างคอมโพเนนต์เว็บที่รวดเร็วและใช้ทรัพยากรน้อย ซึ่งทำงานได้กับทุกเฟรมเวิร์ก หรือไม่ต้องใช้เฟรมเวิร์กเลย Lit ช่วยให้คุณสามารถสร้างคอมโพเนนต์ แอปพลิเคชัน ระบบการออกแบบ และอื่นๆ ที่แชร์ได้
สิ่งที่คุณจะได้เรียนรู้
วิธีแปลแนวคิดปฏิกิริยาต่างๆ เป็นภาษา Lit เช่น
- JSX และ เทมเพลท
- ส่วนประกอบและ ของตกแต่ง
- รัฐและ วงจร
- ฮุก
- เด็ก
- การอ้างอิง
- สถานะสื่อกลาง
สิ่งที่คุณจะสร้าง
ในตอนท้ายของ Codelab นี้จะแปลงแนวคิดคอมโพเนนต์ React เป็นแอนะล็อก Lit ของตัวเองได้
สิ่งที่ต้องมี
- Chrome, Safari, Firefox หรือ Edge เวอร์ชันล่าสุด
- ความรู้เกี่ยวกับ HTML, CSS, JavaScript และเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome
- ความรู้เรื่องปฏิกิริยา
- (ขั้นสูง) หากคุณต้องการประสบการณ์ในการพัฒนาที่ดีที่สุด ให้ดาวน์โหลดโค้ด VS นอกจากนี้ คุณจะต้องมีปลั๊กอินที่มีไฟสำหรับ VS Code และ NPM
2. Lit กับ React
แนวคิดและความสามารถของ Lit คล้ายคลึงกับ React ในหลายๆ ด้าน แต่ Lit มีข้อแตกต่างและความแตกต่างที่สำคัญบางประการดังนี้
มีขนาดเล็ก
Lit มีขนาดเล็กมาก เพราะมีขนาดประมาณ 5 KB และลดขนาดลงเป็น gzip เมื่อเทียบกับ React + ReactDOM ที่มี 40 KB ขึ้นไป
รวดเร็ว
ในการเปรียบเทียบแบบสาธารณะที่เปรียบเทียบระบบเทมเพลตของ Lit, lit-html กับ VDOM ของ React นั้น lit-html ทำงานได้เร็วกว่า 8-10% เมื่อเทียบกับ React ในกรณีที่แย่ที่สุด และเร็วกว่า 50%+ ในกรณีการใช้งานที่พบบ่อยที่สุด
LitElement (คลาสฐานคอมโพเนนต์ของ Lite) เพิ่มค่าใช้จ่ายขั้นต่ำให้กับ lit-html แต่มีประสิทธิภาพดีกว่า React ขึ้น 16-30% เมื่อเปรียบเทียบฟีเจอร์คอมโพเนนต์ เช่น การใช้หน่วยความจำ การโต้ตอบ และเวลาเริ่มต้น
ไม่ต้องมีบิลด์
ด้วยฟีเจอร์ใหม่ๆ ของเบราว์เซอร์ เช่น โมดูล ES และสัญพจน์ของเทมเพลตที่ติดแท็ก ทำให้ Lit ไม่จำเป็นต้องคอมไพล์เพื่อเรียกใช้ ซึ่งหมายความว่าคุณจะตั้งค่าสภาพแวดล้อมในการพัฒนาซอฟต์แวร์ได้ด้วยแท็กสคริปต์ + เบราว์เซอร์ + เซิร์ฟเวอร์ ซึ่งคุณกำลังเริ่มต้นใช้งานอยู่
ด้วยโมดูล ES และ CDN ที่ทันสมัย เช่น Skypack หรือ UNPKG คุณอาจไม่จำเป็นต้องใช้ NPM ในการเริ่มต้นใช้งาน
อย่างไรก็ตาม คุณยังคงสร้างและเพิ่มประสิทธิภาพโค้ด Lit ได้หากต้องการ การรวมนักพัฒนาซอฟต์แวร์ล่าสุดเกี่ยวกับโมดูล ES เนทีฟเป็นผลดีสำหรับ Lit Lit เป็นเพียง JavaScript ปกติและไม่จำเป็นต้องมี CLI เฉพาะเฟรมเวิร์กหรือการจัดการบิลด์
ไม่ยึดติดกับเฟรมเวิร์ก
คอมโพเนนต์ของ Lit สร้างขึ้นจากชุดมาตรฐานของเว็บที่เรียกว่าคอมโพเนนต์ของเว็บ ซึ่งหมายความว่าการสร้างคอมโพเนนต์ใน Lit จะใช้งานได้ในเฟรมเวิร์กในปัจจุบันและอนาคต หากอุปกรณ์รองรับองค์ประกอบ HTML ก็จะรองรับคอมโพเนนต์เว็บด้วย
ปัญหาเดียวเกี่ยวกับการทำงานร่วมกันของเฟรมเวิร์กคือเมื่อเฟรมเวิร์กมีการรองรับ DOM แบบจำกัด React เป็นหนึ่งในเฟรมเวิร์กเหล่านี้ แต่ช่วยให้สามารถหลบเลี่ยงผ่าน Refs ได้ ส่วน Refs ใน React ก็ไม่ใช่ประสบการณ์การใช้งานที่ดีสำหรับนักพัฒนาแอป
ทีม Lit ได้ทำโปรเจ็กต์ทดลองที่ชื่อ @lit-labs/react
ซึ่งจะแยกวิเคราะห์คอมโพเนนต์ Lit ของคุณโดยอัตโนมัติและสร้าง Wrapper ความรู้สึกเพื่อให้คุณไม่ต้องใช้การอ้างอิง
นอกจากนี้ องค์ประกอบที่กำหนดเองทุกที่จะแสดงให้คุณเห็นว่าเฟรมเวิร์กและไลบรารีใดทำงานได้ดีกับองค์ประกอบที่กำหนดเอง
การสนับสนุน TypeScript เฟิร์สคลาส
แม้ว่าจะเขียนโค้ด Lit ทั้งหมดได้ใน JavaScript แต่ Lit เขียนด้วย TypeScript และทีม Lit แนะนำให้นักพัฒนาซอฟต์แวร์ใช้ TypeScript ด้วย
ทีม Lit ได้ทำงานร่วมกับชุมชน Lit เพื่อช่วยรักษาโปรเจ็กต์ที่นำการตรวจสอบประเภทสคริปต์และอัจฉริยะมาสู่เทมเพลตของ Lit ทั้งในส่วนของการพัฒนาและช่วงเวลาในการสร้างด้วย lit-analyzer
และ lit-plugin
เครื่องมือสำหรับนักพัฒนาซอฟต์แวร์มีอยู่ในเบราว์เซอร์
คอมโพเนนต์ Lite เป็นเพียงองค์ประกอบ HTML ใน DOM ซึ่งหมายความว่าในการตรวจสอบคอมโพเนนต์ คุณไม่จำเป็นต้องติดตั้งเครื่องมือหรือไฟล์ปฏิบัติการใดๆ สำหรับเบราว์เซอร์
คุณสามารถเปิดเครื่องมือสำหรับนักพัฒนาเว็บ เลือกองค์ประกอบ และสำรวจคุณสมบัติหรือสถานะขององค์ประกอบนั้นได้
สร้างขึ้นโดยคำนึงถึงการแสดงผลฝั่งเซิร์ฟเวอร์ (SSR)
Lit 2 สร้างขึ้นโดยคำนึงถึงการรองรับ SSR ตอนที่เขียน Codelab นี้ ทีม Lit ยังไม่ได้เปิดตัวเครื่องมือ SSR ในรูปแบบที่เสถียร แต่ทีม Lit ได้ติดตั้งใช้งานคอมโพเนนต์ที่แสดงผลฝั่งเซิร์ฟเวอร์ในผลิตภัณฑ์ต่างๆ ของ Google แล้ว และได้ทดสอบ SSR ภายในแอปพลิเคชัน React ทีม Lit คาดว่าจะมีการเปิดตัวเครื่องมือเหล่านี้สู่ภายนอกใน GitHub ในเร็วๆ นี้
ในระหว่างนี้ คุณสามารถติดตามความคืบหน้าของทีม Lit ได้ที่นี่
ได้ข้อเสนอต่ำ
Lit ไม่จำเป็นต้องมีความมุ่งมั่นในการใช้งานมากนัก คุณสร้างคอมโพเนนต์ใน Lit และเพิ่มลงในโปรเจ็กต์ที่มีอยู่ได้ หากคุณไม่ชอบ ก็ไม่ต้องแปลงทั้งแอปพร้อมกันเนื่องจากคอมโพเนนต์ของเว็บใช้งานได้ในเฟรมเวิร์กอื่นๆ แล้ว
คุณได้สร้างแอปใน Lit ขึ้นมาทั้งแอปแล้วและต้องการเปลี่ยนไปใช้แอปอื่นไหม คุณสามารถวางแอปพลิเคชัน Lit ปัจจุบันไว้ในเฟรมเวิร์กใหม่ และย้ายทุกสิ่งที่ต้องการไปยังคอมโพเนนต์ของเฟรมเวิร์กใหม่ได้
นอกจากนี้ เฟรมเวิร์กสมัยใหม่จำนวนมากรองรับเอาต์พุตในคอมโพเนนต์ของเว็บ ซึ่งหมายความว่าโดยทั่วไปแล้วจะพอดีกับองค์ประกอบ Lit ด้วย
3. การตั้งค่า สำรวจสนามเด็กเล่น
มี 2 วิธีในการทำ Codelab นี้ ได้แก่
- ซึ่งทำได้ทั้งแบบออนไลน์และในเบราว์เซอร์
- (ขั้นสูง) คุณสามารถดำเนินการดังกล่าวได้ในเครื่องของคุณเองโดยใช้ VS Code
การเข้าถึงโค้ด
ตลอดทั้ง Codelab จะมีลิงก์ไปยัง Lit Play แบบ "จำลอง" ดังนี้
สนามเด็กเล่นเป็นแซนด์บ็อกซ์โค้ดที่ทำงานในเบราว์เซอร์ได้อย่างเต็มรูปแบบ เครื่องมือนี้สามารถคอมไพล์และเรียกใช้ไฟล์ TypeScript และ JavaScript และสามารถแก้ไขการนำเข้าไปยังโมดูลโหนดได้โดยอัตโนมัติ เช่น
// before
import './my-file.js';
import 'lit';
// after
import './my-file.js';
import 'https://cdn.skypack.dev/lit';
คุณสามารถทำตามบทแนะนำทั้งหมดใน Lit Play โดยใช้จุดตรวจสอบเหล่านี้เป็นจุดเริ่มต้น หากคุณใช้ VS Code คุณสามารถใช้จุดตรวจเหล่านี้เพื่อดาวน์โหลดโค้ดเริ่มต้นสำหรับขั้นตอนใดๆ และใช้จุดตรวจสอบในการตรวจสอบงานได้
สำรวจ UI สนามเด็กเล่นที่มีไฟสว่าง
ภาพหน้าจอ UI ของ Litground ที่ไฮไลต์ส่วนต่างๆ ที่คุณจะใช้ใน Codelab นี้
- ตัวเลือกไฟล์ สังเกตปุ่มบวก...
- เครื่องมือแก้ไขไฟล์
- ตัวอย่างโค้ด
- ปุ่มโหลดซ้ำ
- ปุ่มดาวน์โหลด
การตั้งค่า VS Code (ขั้นสูง)
ประโยชน์ที่ได้รับจากการตั้งค่า VS Code มีดังนี้
- การตรวจสอบประเภทเทมเพลต
- เทมเพลตอัจฉริยะและ การเติมข้อความอัตโนมัติ
หากคุณมีโค้ด NPM, VS (ที่มีปลั๊กอินปลั๊กอินไฟ) ติดตั้งไว้อยู่แล้ว และทราบวิธีใช้สภาพแวดล้อมดังกล่าว คุณเพียงดาวน์โหลดและเริ่มต้นโปรเจ็กต์เหล่านี้โดยทำตามขั้นตอนต่อไปนี้
- กดปุ่มดาวน์โหลด
- แตกเนื้อหาของไฟล์ tar ลงในไดเรกทอรี
- (หาก TS) ตั้งค่า tsconfig อย่างรวดเร็ว ซึ่งแสดงผลโมดูล es และ es2015+
- ติดตั้งเซิร์ฟเวอร์ dev ที่สามารถแก้ปัญหาตัวระบุโมดูลเปล่า (ทีม Lit แนะนำ @web/dev-server)
- นี่คือตัวอย่าง
package.json
- นี่คือตัวอย่าง
- เรียกใช้เซิร์ฟเวอร์การพัฒนาและเปิดเบราว์เซอร์ของคุณ (ถ้าคุณใช้ @web/dev-server คุณสามารถใช้
npx web-dev-server --node-resolve --watch --open
ได้)- หากคุณใช้ตัวอย่าง
package.json
ให้ใช้npm run dev
- หากคุณใช้ตัวอย่าง
4. JSX และ เทมเพลท
ในส่วนนี้ คุณจะได้เรียนรู้พื้นฐานของการสร้างเทมเพลตใน Lit
JSX และ เทมเพลตของ Lit
JSX เป็นส่วนขยายไวยากรณ์ไปยัง JavaScript ที่ช่วยให้ผู้ใช้ React เขียนเทมเพลตในโค้ด JavaScript ของตนเองได้อย่างง่ายดาย เทมเพลตของ Lite มีวัตถุประสงค์ที่คล้ายกัน กล่าวคือ แสดง UI ของคอมโพเนนต์ในฐานะฟังก์ชันของสถานะ
ไวยากรณ์พื้นฐาน
ใน React คุณจะแสดงผล Hello World ของ JSX ดังนี้
import 'react';
import ReactDOM from 'react-dom';
const name = 'Josh Perez';
const element = (
<>
<h1>Hello, {name}</h1>
<div>How are you?</div>
</>
);
ReactDOM.render(
element,
mountNode
);
ในตัวอย่างข้างต้น มีองค์ประกอบ 2 รายการและ "name" รวมอยู่ด้วย ตัวแปร ใน Lit คุณจะต้องดำเนินการต่อไปนี้:
import {html, render} from 'lit';
const name = 'Josh Perez';
const element = html`
<h1>Hello, ${name}</h1>
<div>How are you?</div>`;
render(
element,
mountNode
);
โปรดสังเกตว่าเทมเพลต Lit ไม่จำเป็นต้องใช้เศษส่วนความรู้สึกเพื่อจัดกลุ่มองค์ประกอบหลายรายการในเทมเพลต
ใน Lit เทมเพลตจะห่อด้วยเทมเพลตที่ติดแท็ก html
ชื่อ LIT ซึ่งจะเป็นชื่อที่ Lit ได้รับ
ค่าเทมเพลต
เทมเพลต Lit ยอมรับเทมเพลต Lit อื่นๆ ซึ่งเรียกว่า TemplateResult
ได้ ตัวอย่างเช่น ใส่ name
ไว้ในแท็กตัวเอียง (<i>
) และห่อด้วยเทมเพลตที่ติดแท็กตามตัวอักษร N.B ตรวจสอบว่าได้ใช้เครื่องหมายแบ็กทิก (`
) ไม่ใช่อักขระเครื่องหมายคำพูดเดี่ยว ('
)
import {html, render} from 'lit';
const name = html`<i>Josh Perez</i>`;
const element = html`
<h1>Hello, ${name}</h1>
<div>How are you?</div>`;
render(
element,
mountNode
);
Lit TemplateResult
ยอมรับอาร์เรย์, สตริง, TemplateResult
อื่นๆ และคำสั่งได้
สำหรับแบบฝึกหัด ให้ลองแปลงโค้ด React ต่อไปนี้เป็น Lit
const itemsToBuy = [
<li>Bananas</li>,
<li>oranges</li>,
<li>apples</li>,
<li>grapes</li>
];
const element = (
<>
<h1>Things to buy:</h1>
<ol>
{itemsToBuy}
</ol>
</>);
ReactDOM.render(
element,
mountNode
);
คำตอบ:
import {html, render} from 'lit';
const itemsToBuy = [
html`<li>Bananas</li>`,
html`<li>oranges</li>`,
html`<li>apples</li>`,
html`<li>grapes</li>`
];
const element = html`
<h1>Things to buy:</h1>
<ol>
${itemsToBuy}
</ol>`;
render(
element,
mountNode
);
การส่งผ่านและอุปกรณ์ประกอบฉาก
ความแตกต่างที่สำคัญที่สุดอย่างหนึ่งระหว่างไวยากรณ์ของ JSX และ Lit คือไวยากรณ์การเชื่อมโยงข้อมูล ตัวอย่างเช่น นำอินพุตของรีแอ็กชันนี้ไปใช้กับการเชื่อมโยง
const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element =
<input
disabled={disabled}
className={`static-class ${myClass}`}
defaultValue={value}/>;
ReactDOM.render(
element,
mountNode
);
ในตัวอย่างข้างต้น อินพุตได้รับการกำหนดซึ่งจะดำเนินการต่อไปนี้
- ตั้งค่าให้ปิดใช้เป็นตัวแปรที่กำหนด (ในกรณีนี้คือเท็จ)
- ตั้งค่าคลาสเป็น
static-class
บวกตัวแปร (ในกรณีนี้คือ"static-class my-class"
) - ตั้งค่าเริ่มต้น
ใน Lit คุณจะต้องดำเนินการต่อไปนี้:
import {html, render} from 'lit';
const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element = html`
<input
?disabled=${disabled}
class="static-class ${myClass}"
.value=${value}>`;
render(
element,
mountNode
);
ในตัวอย่าง Lit จะมีการเพิ่มการเชื่อมโยงบูลีนเพื่อสลับแอตทริบิวต์ disabled
ถัดไป จะเกิดการเชื่อมโยงกับแอตทริบิวต์ class
โดยตรงแทนที่จะเป็น className
คุณเพิ่มการเชื่อมโยงหลายรายการในแอตทริบิวต์ class
ได้ เว้นแต่ว่าคุณกำลังใช้คำสั่ง classMap
ซึ่งเป็นตัวช่วยการประกาศสำหรับคลาสสลับ
สุดท้าย มีการตั้งค่าพร็อพเพอร์ตี้ value
ในอินพุต การดำเนินการนี้ต่างจากใน React ตรงที่จะไม่ตั้งค่าองค์ประกอบอินพุตเป็นแบบอ่านอย่างเดียวเพราะเป็นไปตามการใช้งานแบบเนทีฟและลักษณะการทำงานของอินพุต
ไวยากรณ์การเชื่อมโยง Lit Pro
html`<my-element ?attribute-name=${booleanVar}>`;
- คำนำหน้า
?
คือไวยากรณ์การเชื่อมโยงสำหรับการสลับแอตทริบิวต์ในองค์ประกอบ - เทียบเท่ากับ
inputRef.toggleAttribute('attribute-name', booleanVar)
- มีประโยชน์สำหรับองค์ประกอบที่ใช้
disabled
เนื่องจากdisabled="false"
ยังคงอ่านค่าว่าเป็นจริงโดย DOM เนื่องจากinputElement.hasAttribute('disabled') === true
html`<my-element .property-name=${anyVar}>`;
- คำนำหน้า
.
คือไวยากรณ์การเชื่อมโยงสำหรับการตั้งค่าพร็อพเพอร์ตี้ขององค์ประกอบ - เทียบเท่ากับ
inputRef.propertyName = anyVar
- เหมาะสำหรับการส่งข้อมูลที่ซับซ้อน เช่น ออบเจ็กต์ อาร์เรย์ หรือคลาส
html`<my-element attribute-name=${stringVar}>`;
- เชื่อมโยงกับแอตทริบิวต์ขององค์ประกอบ
- เทียบเท่ากับ
inputRef.setAttribute('attribute-name', stringVar)
- เหมาะสำหรับค่าพื้นฐาน ตัวเลือกกฎสไตล์ และ querySelector
ตัวแฮนเดิลการส่ง
const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element =
<input
onClick={() => console.log('click')}
onChange={e => console.log(e.target.value)} />;
ReactDOM.render(
element,
mountNode
);
ในตัวอย่างข้างต้น อินพุตได้รับการกำหนดซึ่งจะดำเนินการต่อไปนี้
- บันทึกคำว่า "คลิก" เมื่อมีการคลิกอินพุต
- บันทึกค่าของข้อมูลที่ป้อนเมื่อผู้ใช้พิมพ์อักขระ
ใน Lit คุณจะต้องดำเนินการต่อไปนี้:
import {html, render} from 'lit';
const disabled = false;
const label = 'my label';
const myClass = 'my-class';
const value = 'my value';
const element = html`
<input
@click=${() => console.log('click')}
@input=${e => console.log(e.target.value)}>`;
render(
element,
mountNode
);
ในตัวอย่าง Lit มีการเพิ่ม Listener ลงในเหตุการณ์ click
ที่มี @click
ถัดไป แทนที่จะใช้ onChange
จะมีการเชื่อมโยงกับเหตุการณ์ input
แบบเนทีฟของ <input>
เนื่องจากเหตุการณ์ change
แบบเนทีฟเริ่มทำงานเฉพาะใน blur
(แสดงบทคัดย่อจากเหตุการณ์เหล่านี้)
ไวยากรณ์ของเครื่องจัดการเหตุการณ์ Lit
html`<my-element @event-name=${() => {...}}></my-element>`;
- คำนำหน้า
@
คือไวยากรณ์การเชื่อมโยงสำหรับ Listener เหตุการณ์ - เทียบเท่ากับ
inputRef.addEventListener('event-name', ...)
- ใช้ชื่อเหตุการณ์ DOM ดั้งเดิม
5. ส่วนประกอบและ ของตกแต่ง
ในส่วนนี้ คุณจะได้เรียนรู้เกี่ยวกับคอมโพเนนต์และฟังก์ชันของคลาส Lit เราจะกล่าวถึงสถานะและ Hook อย่างละเอียดในหัวข้อถัดๆ ไป
ส่วนประกอบของชั้นเรียนและ LitElement
Lit ที่เทียบเท่ากับคอมโพเนนต์คลาสรีแอ็กชันคือ LitElement และแนวคิดของ Lit เกี่ยวกับ "สมบัติเชิงปฏิกิริยา" เป็นการรวมพร็อพและสถานะของ React เช่น
import React from 'react';
import ReactDOM from 'react-dom';
class Welcome extends React.Component {
constructor(props) {
super(props);
this.state = {name: ''};
}
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
const element = <Welcome name="Elliott"/>
ReactDOM.render(
element,
mountNode
);
ในตัวอย่างด้านบน มีคอมโพเนนต์ React ที่มีลักษณะดังนี้
- แสดงภาพ
name
- ตั้งค่าเริ่มต้น
name
เป็นสตริงว่างเปล่า (""
) - มอบหมาย
name
ใหม่ให้"Elliott"
คุณจะใช้วิธีการนี้ใน LitElement
ใน TypeScript:
import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
@property({type: String})
name = '';
render() {
return html`<h1>Hello, ${this.name}</h1>`
}
}
ใน JavaScript:
import {LitElement, html} from 'lit';
class WelcomeBanner extends LitElement {
static get properties() {
return {
name: {type: String}
}
}
constructor() {
super();
this.name = '';
}
render() {
return html`<h1>Hello, ${this.name}</h1>`
}
}
customElements.define('welcome-banner', WelcomeBanner);
และในไฟล์ HTML
<!-- index.html -->
<head>
<script type="module" src="./index.js"></script>
</head>
<body>
<welcome-banner name="Elliott"></welcome-banner>
</body>
การตรวจสอบสิ่งที่เกิดขึ้นในตัวอย่างด้านบน
@property({type: String})
name = '';
- กำหนดพร็อพเพอร์ตี้เชิงรับสาธารณะ ซึ่งเป็นส่วนหนึ่งของ API สาธารณะของคอมโพเนนต์
- แสดงแอตทริบิวต์ (โดยค่าเริ่มต้น) และพร็อพเพอร์ตี้ในคอมโพเนนต์
- กำหนดวิธีแปลแอตทริบิวต์ของคอมโพเนนต์ (ซึ่งก็คือสตริง) เป็นค่า
static get properties() {
return {
name: {type: String}
}
}
- แท็กนี้ทำหน้าที่เหมือนกับตัวตกแต่ง TS ของ
@property
แต่จะทำงานแบบเนทีฟใน JavaScript
render() {
return html`<h1>Hello, ${this.name}</h1>`
}
- โดยจะเรียกเมื่อมีการเปลี่ยนแปลงคุณสมบัติเชิงรับ
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
...
}
- การดำเนินการนี้จะเชื่อมโยงชื่อแท็กองค์ประกอบ HTML กับการกำหนดคลาส
- ตามมาตรฐานองค์ประกอบที่กำหนดเอง ชื่อแท็กต้องมีขีดกลางสั้น (-)
this
ใน LitElement หมายถึงอินสแตนซ์ขององค์ประกอบที่กำหนดเอง (ในกรณีนี้คือ<welcome-banner>
)
customElements.define('welcome-banner', WelcomeBanner);
- นี่คือ JavaScript ที่เทียบเท่ากับการตกแต่งด้าน TS ของ
@customElement
<head>
<script type="module" src="./index.js"></script>
</head>
- นำเข้าคำจำกัดความองค์ประกอบที่กำหนดเอง
<body>
<welcome-banner name="Elliott"></welcome-banner>
</body>
- เพิ่มองค์ประกอบที่กำหนดเองลงในหน้าเว็บ
- ตั้งค่าพร็อพเพอร์ตี้
name
เป็น'Elliott'
คอมโพเนนต์ฟังก์ชัน
Lit ไม่มีการตีความ 1:1 สำหรับคอมโพเนนต์ฟังก์ชันเนื่องจากไม่ได้ใช้ JSX หรือตัวประมวลผลล่วงหน้า แม้ว่าการสร้างฟังก์ชันที่ใช้คุณสมบัติและแสดงผล DOM ตามคุณสมบัติเหล่านั้นนั้นค่อนข้างง่าย เช่น
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Elliott"/>
ReactDOM.render(
element,
mountNode
);
ใน Lit จะเป็น:
import {html, render} from 'lit';
function Welcome(props) {
return html`<h1>Hello, ${props.name}</h1>`;
}
render(
Welcome({name: 'Elliott'}),
document.body.querySelector('#root')
);
6. รัฐและ วงจร
ในส่วนนี้ คุณจะได้เรียนรู้เกี่ยวกับสถานะและวงจรของ Lit
รัฐ
แนวคิดของ Lit เกี่ยวกับ "คุณสมบัติเชิงรับ" เป็นการผสมผสานสถานะของ React และอุปกรณ์ประกอบฉาก เมื่อมีการเปลี่ยนแปลงคุณสมบัติเชิงรับ จะทริกเกอร์วงจรของคอมโพเนนต์ได้ พร็อพเพอร์ตี้เชิงรับมีด้วยกัน 2 ตัวแปร ได้แก่
พร็อพเพอร์ตี้เชิงปฏิกิริยาแบบสาธารณะ
// React
import React from 'react';
class MyEl extends React.Component {
constructor(props) {
super(props)
this.state = {name: 'there'}
}
componentWillReceiveProps(nextProps) {
if (this.props.name !== nextProps.name) {
this.setState({name: nextProps.name})
}
}
}
// Lit (TS)
import {LitElement} from 'lit';
import {property} from 'lit/decorators.js';
class MyEl extends LitElement {
@property() name = 'there';
}
- กำหนดโดย
@property
- คล้ายกับอุปกรณ์ประกอบฉากและสถานะของ React แต่เปลี่ยนแปลงได้
- API สาธารณะที่ผู้บริโภคของคอมโพเนนต์เข้าถึงและตั้งค่า
สถานะการโต้ตอบภายใน
// React
import React from 'react';
class MyEl extends React.Component {
constructor(props) {
super(props)
this.state = {name: 'there'}
}
}
// Lit (TS)
import {LitElement} from 'lit';
import {state} from 'lit/decorators.js';
class MyEl extends LitElement {
@state() name = 'there';
}
- กำหนดโดย
@state
- คล้ายกับสถานะของรีแอ็กชันแต่เปลี่ยนแปลงได้
- สถานะภายในส่วนตัวที่โดยทั่วไปจะมีการเข้าถึงจากภายในคอมโพเนนต์หรือคลาสย่อย
อายุการใช้งาน
วงจรของ Lit นั้นค่อนข้างคล้ายกับวงจรของ React แต่มีความแตกต่างที่เห็นได้ชัด
constructor
// React (js)
import React from 'react';
class MyEl extends React.Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
this._privateProp = 'private';
}
}
// Lit (ts)
class MyEl extends LitElement {
@property({type: Number}) counter = 0;
private _privateProp = 'private';
}
// Lit (js)
class MyEl extends LitElement {
static get properties() {
return { counter: {type: Number} }
}
constructor() {
this.counter = 0;
this._privateProp = 'private';
}
}
- จำนวนที่เทียบเท่าคือ
constructor
เช่นกัน - ไม่จำเป็นต้องส่งต่อสิ่งใดไปยัง Super Call
- เรียกใช้โดย (ไม่รวมทั้งหมด):
document.createElement
document.innerHTML
new ComponentClass()
- หากมีชื่อแท็กที่ไม่ได้อัปเกรดอยู่ในหน้านั้น และโหลดคำจำกัดความและลงทะเบียนกับ
@customElement
หรือcustomElements.define
แล้ว
- ฟังก์ชันคล้ายกับ
constructor
ของ React
render
// React
render() {
return <div>Hello World</div>
}
// Lit
render() {
return html`<div>Hello World</div>`;
}
- จำนวนที่เทียบเท่าคือ
render
เช่นกัน - แสดงผลลัพธ์ที่แสดงผลได้ เช่น
TemplateResult
หรือstring
เป็นต้น render()
ควรเป็นฟังก์ชันที่ไม่ซับซ้อน คล้ายกับ React- จะแสดงผลต่อโหนดใดก็ตามที่
createRenderRoot()
ส่งคืน (ShadowRoot
โดยค่าเริ่มต้น)
componentDidMount
componentDidMount
คล้ายกับการรวมกันของ Callback วงจรทั้ง firstUpdated
ของ Lit และ connectedCallback
firstUpdated
import Chart from 'chart.js';
// React
componentDidMount() {
this._chart = new Chart(this.chartElRef.current, {...});
}
// Lit
firstUpdated() {
this._chart = new Chart(this.chartEl, {...});
}
- มีการเรียกเมื่อแสดงผลเทมเพลตของคอมโพเนนต์เป็นครั้งแรกในรากของคอมโพเนนต์
- จะมีการเรียกก็ต่อเมื่อองค์ประกอบเชื่อมต่ออยู่เท่านั้น เช่น ไม่เรียกใช้ผ่าน
document.createElement('my-component')
จนกว่าจะมีการเพิ่มโหนดนั้นลงในแผนผัง DOM - ตำแหน่งนี้เหมาะสำหรับการตั้งค่าคอมโพเนนต์ที่กำหนดให้คอมโพเนนต์แสดงผล DOM
- ต่างจากการเปลี่ยนแปลง
componentDidMount
ของ React ที่เป็นพร็อพเพอร์ตี้เชิงรับในfirstUpdated
จะทำให้แสดงผลอีกครั้ง แม้ว่าโดยทั่วไปแล้วเบราว์เซอร์จะจัดกลุ่มการเปลี่ยนแปลงไว้ในเฟรมเดียวกันก็ตาม หากการเปลี่ยนแปลงเหล่านั้นไม่จำเป็นต้องเข้าถึง DOM ของรูท การเปลี่ยนแปลงก็ควรอยู่ในwillUpdate
connectedCallback
// React
componentDidMount() {
this.window.addEventListener('resize', this.boundOnResize);
}
// Lit
connectedCallback() {
super.connectedCallback();
this.window.addEventListener('resize', this.boundOnResize);
}
- เรียกใช้เมื่อใดก็ตามที่องค์ประกอบที่กำหนดเองถูกแทรกลงในแผนผัง DOM
- คอมโพเนนต์ที่ต่างจาก React คือเมื่อองค์ประกอบที่กำหนดเองถูกปลดออกจาก DOM จะไม่มีการทำลายองค์ประกอบเหล่านั้น จึงสามารถ "เชื่อมต่อ" ได้ หลายครั้ง
- จะไม่มีการโทรหา
firstUpdated
อีก
- จะไม่มีการโทรหา
- มีประโยชน์สำหรับการเริ่มต้น DOM อีกครั้งหรือแนบ Listener เหตุการณ์อีกครั้งซึ่งได้รับการแก้ไขเมื่อยกเลิกการเชื่อมต่อ
- หมายเหตุ: อาจมีการเรียก
connectedCallback
ก่อนfirstUpdated
ดังนั้นในการเรียกครั้งแรก DOM อาจไม่พร้อมให้บริการ
componentDidUpdate
// React
componentDidUpdate(prevProps) {
if (this.props.title !== prevProps.title) {
this._chart.setTitle(this.props.title);
}
}
// Lit (ts)
updated(prevProps: PropertyValues<this>) {
if (prevProps.has('title')) {
this._chart.setTitle(this.title);
}
}
- Litเทียบเท่าคือ
updated
(ใช้ "update" ในภาษาอังกฤษ - มีการเรียก
updated
ในการแสดงภาพเริ่มต้น ซึ่งต่างจาก React - ฟังก์ชันคล้ายกับ
componentDidUpdate
ของ React
componentWillUnmount
// React
componentWillUnmount() {
this.window.removeEventListener('resize', this.boundOnResize);
}
// Lit
disconnectedCallback() {
super.disconnectedCallback();
this.window.removeEventListener('resize', this.boundOnResize);
}
- จำนวนบัญชีที่เทียบเท่ากันกับ
disconnectedCallback
- ซึ่งต่างจากคอมโพเนนต์ React ตรงที่องค์ประกอบที่กำหนดเองถูกปลดออกจาก DOM จะไม่มีการทำลายคอมโพเนนต์
- สิ่งที่ต่างจาก
componentWillUnmount
คือจะเรียกdisconnectedCallback
ว่าหลังการนำองค์ประกอบออกจากแผนผังต้นไม้ - DOM ภายในรูทยังคงแนบอยู่กับแผนผังย่อยของรูท
- มีประโยชน์ในการทำความสะอาด Listener เหตุการณ์และการอ้างอิงที่รั่วไหลเพื่อให้เบราว์เซอร์จัดการขยะเก็บรวบรวมคอมโพเนนต์ได้
การออกกำลังกาย
import React from 'react';
import ReactDOM from 'react-dom';
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
ในตัวอย่างด้านบน มีนาฬิกาแบบง่ายที่ทำงานต่อไปนี้
- ผลการค้นหาจะแสดงข้อความ "สวัสดีทุกคน! คือ" จากนั้นจะแสดงเวลา
- ทุกวินาทีจะอัปเดตนาฬิกา
- เมื่อถอดออกแล้ว เครื่องจะล้างระยะห่างของการเรียกเห็บ
ก่อนอื่น ให้เริ่มต้นด้วยการประกาศคลาสคอมโพเนนต์
// Lit (TS)
// some imports here are imported in advance
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';
@customElement('lit-clock')
class LitClock extends LitElement {
}
// Lit (JS)
// `html` is imported in advance
import {LitElement, html} from 'lit';
class LitClock extends LitElement {
}
customElements.define('lit-clock', LitClock);
ถัดไป ให้เริ่มต้น date
และประกาศว่าเป็นพร็อพเพอร์ตี้เชิงรับภายใน @state
เนื่องจากผู้ใช้คอมโพเนนต์จะไม่ได้ตั้งค่า date
โดยตรง
// Lit (TS)
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';
@customElement('lit-clock')
class LitClock extends LitElement {
@state() // declares internal reactive prop
private date = new Date(); // initialization
}
// Lit (JS)
import {LitElement, html} from 'lit';
class LitClock extends LitElement {
static get properties() {
return {
// declares internal reactive prop
date: {state: true}
}
}
constructor() {
super();
// initialization
this.date = new Date();
}
}
customElements.define('lit-clock', LitClock);
จากนั้นแสดงผลเทมเพลต
// Lit (JS & TS)
render() {
return html`
<div>
<h1>Hello, World!</h1>
<h2>It is ${this.date.toLocaleTimeString()}.</h2>
</div>
`;
}
ต่อไป ให้ใช้วิธีการทำเครื่องหมาย
tick() {
this.date = new Date();
}
ลำดับต่อไปคือการปรับใช้ componentDidMount
แอนะล็อก Lit เป็นการผสมผสานระหว่าง firstUpdated
และ connectedCallback
ในกรณีของคอมโพเนนต์นี้ การเรียกใช้ tick
ด้วย setInterval
ไม่จำเป็นต้องเข้าถึง DOM ภายในรูท นอกจากนี้ ระบบจะล้างช่วงเวลาเมื่อนำองค์ประกอบออกจากโครงสร้างเอกสาร ดังนั้นหากมีการใส่กลับเข้าไปใหม่ ช่วงเวลาจะต้องเริ่มต้นอีกครั้ง ดังนั้น connectedCallback
จึงเป็นตัวเลือกที่ดีกว่าสำหรับที่นี่
// Lit (TS)
@customElement('lit-clock')
class LitClock extends LitElement {
@state()
private date = new Date();
// initialize timerId for TS
private timerId = -1 as unknown as ReturnType<typeof setTimeout>;
connectedCallback() {
super.connectedCallback();
this.timerId = setInterval(
() => this.tick(),
1000
);
}
...
}
// Lit (JS)
constructor() {
super();
// initialization
this.date = new Date();
this.timerId = -1; // initialize timerId for JS
}
connectedCallback() {
super.connectedCallback();
this.timerId = setInterval(
() => this.tick(),
1000
);
}
สุดท้าย ให้ล้างช่วงเวลาเพื่อไม่ให้เรียกใช้เครื่องหมายถูกหลังจากยกเลิกการเชื่อมต่อองค์ประกอบจากแผนผังเอกสาร
// Lit (TS & JS)
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.timerId);
}
เมื่อนำทั้งหมดมาประกอบกัน ก็ควรจะมีลักษณะดังนี้
// Lit (TS)
import {LitElement, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';
@customElement('lit-clock')
class LitClock extends LitElement {
@state()
private date = new Date();
private timerId = -1 as unknown as ReturnType<typeof setTimeout>;
connectedCallback() {
super.connectedCallback();
this.timerId = setInterval(
() => this.tick(),
1000
);
}
tick() {
this.date = new Date();
}
render() {
return html`
<div>
<h1>Hello, World!</h1>
<h2>It is ${this.date.toLocaleTimeString()}.</h2>
</div>
`;
}
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.timerId);
}
}
// Lit (JS)
import {LitElement, html} from 'lit';
class LitClock extends LitElement {
static get properties() {
return {
date: {state: true}
}
}
constructor() {
super();
this.date = new Date();
}
connectedCallback() {
super.connectedCallback();
this.timerId = setInterval(
() => this.tick(),
1000
);
}
tick() {
this.date = new Date();
}
render() {
return html`
<div>
<h1>Hello, World!</h1>
<h2>It is ${this.date.toLocaleTimeString()}.</h2>
</div>
`;
}
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.timerId);
}
}
customElements.define('lit-clock', LitClock);
7. ฮุก
ในส่วนนี้ คุณจะได้ดูวิธีแปลแนวคิด React Hook เป็นภาษา Lit
แนวคิดของ React Hook
รีแอ็กชันฮุกช่วยให้คอมโพเนนต์ฟังก์ชัน "ฮุก" ได้ เข้าสู่สถานะนี้ การดำเนินการนี้มีประโยชน์หลายประการ
- ช่วยให้การนำตรรกะที่เก็บสถานะกลับมาใช้ใหม่ได้ง่ายขึ้น
- ช่วยแบ่งคอมโพเนนต์ให้เป็นฟังก์ชันที่เล็กลง
นอกจากนี้ การมุ่งเน้นที่คอมโพเนนต์ที่อิงตามฟังก์ชันซึ่งแก้ปัญหาบางอย่างเกี่ยวกับไวยากรณ์ตามคลาสของ React มีดังนี้
- ต้องผ่าน
props
จากconstructor
ไปยังsuper
- การเริ่มต้นพร็อพเพอร์ตี้ที่ไม่เป็นระเบียบใน
constructor
- ซึ่งเป็นเหตุผลที่ทีม React ระบุไว้ในขณะนั้นแต่แก้ไขได้ภายใน ES2019
- ปัญหาที่เกิดจาก
this
ไม่ได้อ้างอิงถึงคอมโพเนนต์อีกต่อไป
แสดงความรู้สึกแนวคิดฮุกใน Lit
ตามที่ระบุไว้ในคอมโพเนนต์และ ส่วน Props Lit ไม่ได้นำเสนอวิธีสร้างองค์ประกอบที่กำหนดเองจากฟังก์ชัน แต่ LitElement จะแก้ไขปัญหาหลักๆ ส่วนใหญ่เกี่ยวกับคอมโพเนนต์คลาส React เช่น
// React (at the time of making hooks)
import React from 'react';
import ReactDOM from 'react-dom';
class MyEl extends React.Component {
constructor(props) {
super(props); // Leaky implementation
this.state = {count: 0};
this._chart = null; // Deemed messy
}
render() {
return (
<>
<div>Num times clicked {count}</div>
<button onClick={this.clickCallback}>click me</button>
</>
);
}
clickCallback() {
// Errors because `this` no longer refers to the component
this.setState({count: this.count + 1});
}
}
// Lit (ts)
class MyEl extends LitElement {
@property({type: Number}) count = 0; // No need for constructor to set state
private _chart = null; // Public class fields introduced to JS in 2019
render() {
return html`
<div>Num times clicked ${count}</div>
<button @click=${this.clickCallback}>click me</button>`;
}
private clickCallback() {
// No error because `this` refers to component
this.count++;
}
}
Lit จัดการกับปัญหาเหล่านี้อย่างไร
constructor
ไม่รับอาร์กิวเมนต์- การเชื่อมโยง
@event
ทั้งหมดจะเชื่อมโยงกับthis
โดยอัตโนมัติ this
ในกรณีส่วนใหญ่จะหมายถึงการอ้างอิงขององค์ประกอบที่กำหนดเอง- ในตอนนี้สามารถสร้างพร็อพเพอร์ตี้ของชั้นเรียนเป็นสมาชิกในชั้นเรียนได้แล้ว การดำเนินการนี้จะช่วยจัดระเบียบการใช้งานที่อิงตามตัวสร้าง
ตัวควบคุมการโต้ตอบ
แนวคิดหลักเบื้องหลัง Hooks อยู่ใน Lit ในฐานะที่เป็นเครื่องมือควบคุมเชิงรับ รูปแบบตัวควบคุมแบบรีแอคทีฟช่วยให้แชร์ตรรกะแบบเก็บสถานะได้ แบ่งคอมโพเนนต์ออกเป็นบิตย่อยแบบโมดูลย่อยมากขึ้น ตลอดจนเข้าถึงวงจรการอัปเดตขององค์ประกอบ
ตัวควบคุมเชิงรับเป็นอินเทอร์เฟซของออบเจ็กต์ที่สามารถดึงดูดเข้าสู่วงจรการอัปเดตของโฮสต์ตัวควบคุม เช่น LitElement
วงจรของ ReactiveController
และ reactiveControllerHost
คือ
interface ReactiveController {
hostConnected(): void;
hostUpdate(): void;
hostUpdated(): void;
hostDisconnected(): void;
}
interface ReactiveControllerHost {
addController(controller: ReactiveController): void;
removeController(controller: ReactiveController): void;
requestUpdate(): void;
readonly updateComplete: Promise<boolean>;
}
การสร้างตัวควบคุมเชิงรับและแนบกับโฮสต์ที่มี addController
จะทำให้ระบบเรียกใช้วงจรของตัวควบคุมควบคู่ไปกับตัวควบคุมของโฮสต์ ตัวอย่างเช่น ดูตัวอย่างนาฬิกาจาก State & ส่วนวงจร
import React from 'react';
import ReactDOM from 'react-dom';
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
ในตัวอย่างด้านบน มีนาฬิกาแบบง่ายที่ทำงานต่อไปนี้
- ผลการค้นหาจะแสดงข้อความ "สวัสดีทุกคน! คือ" จากนั้นจะแสดงเวลา
- ทุกวินาทีจะอัปเดตนาฬิกา
- เมื่อถอดออกแล้ว เครื่องจะล้างระยะห่างของการเรียกเห็บ
การสร้างนั่งร้านคอมโพเนนต์
ก่อนอื่นให้เริ่มต้นด้วยการประกาศคลาสคอมโพเนนต์แล้วเพิ่มฟังก์ชัน render
// Lit (TS) - index.ts
import {LitElement, html} from 'lit';
import {customElement} from 'lit/decorators.js';
@customElement('my-element')
class MyElement extends LitElement {
render() {
return html`
<div>
<h1>Hello, world!</h1>
<h2>It is ${'time to get Lit'}.</h2>
</div>
`;
}
}
// Lit (JS) - index.js
import {LitElement, html} from 'lit';
class MyElement extends LitElement {
render() {
return html`
<div>
<h1>Hello, world!</h1>
<h2>It is ${'time to get Lit'}.</h2>
</div>
`;
}
}
customElements.define('my-element', MyElement);
การสร้างตัวควบคุม
จากนั้นให้สลับไปที่ clock.ts
และสร้างชั้นเรียนสำหรับ ClockController
และตั้งค่า constructor
ดังนี้
// Lit (TS) - clock.ts
import {ReactiveController, ReactiveControllerHost} from 'lit';
export class ClockController implements ReactiveController {
private readonly host: ReactiveControllerHost;
constructor(host: ReactiveControllerHost) {
this.host = host;
host.addController(this);
}
hostConnected() {
}
private tick() {
}
hostDisconnected() {
}
}
// Lit (JS) - clock.js
export class ClockController {
constructor(host) {
this.host = host;
host.addController(this);
}
hostConnected() {
}
tick() {
}
hostDisconnected() {
}
}
คุณสามารถสร้างตัวควบคุมเชิงรับอย่างไรก็ได้ตราบใดที่ยังมีอินเทอร์เฟซ ReactiveController
เดียวกัน แต่การใช้คลาสที่มี constructor
ที่สามารถใช้ในอินเทอร์เฟซ ReactiveControllerHost
รวมถึงพร็อพเพอร์ตี้อื่นๆ ที่จำเป็นในการเริ่มต้นตัวควบคุมนั้นเป็นรูปแบบที่ทีม Lit ต้องการใช้สำหรับกรณีพื้นฐานส่วนใหญ่
ตอนนี้คุณต้องแปล Callback วงจรของ React เป็น Callback ตัวควบคุม กล่าวโดยสรุปคือ
componentDidMount
- ถึง
connectedCallback
ของ LitElement - ถึง
hostConnected
ของตัวควบคุม
- ถึง
ComponentWillUnmount
- ถึง
disconnectedCallback
ของ LitElement - ถึง
hostDisconnected
ของตัวควบคุม
- ถึง
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการแปลวงจรของปฏิกิริยาเป็นวงจรชีวิตของสังคม โปรดดูสถานะและ วงจร
ถัดไป ให้ใช้เมธอด hostConnected
Callback และ tick
และล้างช่วงเวลาใน hostDisconnected
ตามที่ทำในตัวอย่าง State & วงจร
// Lit (TS) - clock.ts
export class ClockController implements ReactiveController {
private readonly host: ReactiveControllerHost;
private interval = 0 as unknown as ReturnType<typeof setTimeout>;
date = new Date();
constructor(host: ReactiveControllerHost) {
this.host = host;
host.addController(this);
}
hostConnected() {
this.interval = setInterval(() => this.tick(), 1000);
}
private tick() {
this.date = new Date();
}
hostDisconnected() {
clearInterval(this.interval);
}
}
// Lit (JS) - clock.js
export class ClockController {
interval = 0;
host;
date = new Date();
constructor(host) {
this.host = host;
host.addController(this);
}
hostConnected() {
this.interval = setInterval(() => this.tick(), 1000);
}
tick() {
this.date = new Date();
}
hostDisconnected() {
clearInterval(this.interval);
}
}
การใช้ตัวควบคุม
หากต้องการใช้ตัวควบคุมนาฬิกา ให้นำเข้าตัวควบคุมแล้วอัปเดตคอมโพเนนต์ใน index.ts
หรือ index.js
// Lit (TS) - index.ts
import {LitElement, html, ReactiveController, ReactiveControllerHost} from 'lit';
import {customElement} from 'lit/decorators.js';
import {ClockController} from './clock.js';
@customElement('my-element')
class MyElement extends LitElement {
private readonly clock = new ClockController(this); // Instantiate
render() {
// Use controller
return html`
<div>
<h1>Hello, world!</h1>
<h2>It is ${this.clock.date.toLocaleTimeString()}.</h2>
</div>
`;
}
}
// Lit (JS) - index.js
import {LitElement, html} from 'lit';
import {ClockController} from './clock.js';
class MyElement extends LitElement {
clock = new ClockController(this); // Instantiate
render() {
// Use controller
return html`
<div>
<h1>Hello, world!</h1>
<h2>It is ${this.clock.date.toLocaleTimeString()}.</h2>
</div>
`;
}
}
customElements.define('my-element', MyElement);
ในการใช้ตัวควบคุม คุณจะต้องสร้างอินสแตนซ์ตัวควบคุมด้วยการส่งผ่านการอ้างอิงไปยังโฮสต์ตัวควบคุม (ซึ่งเป็นคอมโพเนนต์ <my-element>
) แล้วใช้ตัวควบคุมในเมธอด render
การเรียกการแสดงผลอีกครั้งในตัวควบคุม
โปรดสังเกตว่านาฬิกาจะแสดงเวลา แต่เวลาไม่อัปเดต เนื่องจากตัวควบคุมจะตั้งวันที่ทุกวินาที แต่โฮสต์ไม่อัปเดต เนื่องจาก date
มีการเปลี่ยนแปลงในคลาส ClockController
และไม่ใช่คอมโพเนนต์อีกต่อไป ซึ่งหมายความว่าหลังจากตั้งค่า date
บนตัวควบคุมแล้ว โฮสต์จะต้องได้รับแจ้งให้เรียกใช้วงจรการอัปเดตด้วย host.requestUpdate()
// Lit (TS & JS) - clock.ts / clock.js
private tick() {
this.date = new Date();
this.host.requestUpdate();
}
ตอนนี้เวลาน่าจะใกล้หมดแล้ว
สำหรับการเปรียบเทียบในเชิงลึกของ Use Case ที่พบบ่อยเกี่ยวกับ Hook โปรดดูส่วน หัวข้อขั้นสูง - Hook
8. เด็ก
ในส่วนนี้ คุณจะได้เรียนรู้วิธีใช้สล็อตเพื่อจัดการรายการย่อยใน Lit
สล็อตและ เด็ก
ช่องโฆษณาจะเปิดใช้องค์ประกอบโดยให้คุณฝังคอมโพเนนต์ได้
ใน React นั้น เด็กๆ จะสืบทอดกันผ่านอุปกรณ์ประกอบฉาก ช่องเริ่มต้นคือ props.children
และฟังก์ชัน render
จะกำหนดตำแหน่งของช่องโฆษณาเริ่มต้น เช่น
const MyArticle = (props) => {
return <article>{props.children}</article>;
};
โปรดทราบว่า props.children
เป็น React Components ไม่ใช่องค์ประกอบ HTML
ใน Lit องค์ประกอบย่อยต่างๆ จะสร้างขึ้นในฟังก์ชันการแสดงภาพที่มีองค์ประกอบช่อง โปรดทราบว่ารายการย่อยไม่รับค่ามาในลักษณะเดียวกับ React ใน Lit องค์ประกอบย่อยของ HTMLElements จะแนบอยู่กับช่องโฆษณา ไฟล์แนบนี้เรียกว่าการฉายภาพ
@customElement("my-article")
export class MyArticle extends LitElement {
render() {
return html`
<article>
<slot></slot>
</article>
`;
}
}
หลายช่อง
ใน React การเพิ่มสล็อตหลายช่องก็เหมือนกับการรับพร็อพเพอร์เพิ่มมากขึ้น
const MyArticle = (props) => {
return (
<article>
<header>
{props.headerChildren}
</header>
<section>
{props.sectionChildren}
</section>
</article>
);
};
ในทํานองเดียวกัน การเพิ่มองค์ประกอบ <slot>
จะเป็นการสร้างช่องโฆษณาใน Lit มากขึ้น มีการกำหนดสล็อตหลายช่องด้วยแอตทริบิวต์ name
: <slot name="slot-name">
ซึ่งช่วยให้เด็กประกาศได้ว่าจะให้มอบหมายช่องใด
@customElement("my-article")
export class MyArticle extends LitElement {
render() {
return html`
<article>
<header>
<slot name="headerChildren"></slot>
</header>
<section>
<slot name="sectionChildren"></slot>
</section>
</article>
`;
}
}
เนื้อหาเริ่มต้นของสล็อต
สล็อตจะแสดงแผนผังย่อยเมื่อไม่มีโหนดที่ฉายไปยังช่องโฆษณานั้น เมื่อฉายโหนดไปยังสล็อต สล็อตนั้นจะไม่แสดงแผนผังย่อยและแสดงโหนดที่ฉายแทน
@customElement("my-element")
export class MyElement extends LitElement {
render() {
return html`
<section>
<div>
<slot name="slotWithDefault">
<p>
This message will not be rendered when children are attached to this slot!
<p>
</slot>
</div>
</section>
`;
}
}
กำหนดรายการย่อยให้กับสล็อต
ใน React จะมีการกำหนดช่องโฆษณาย่อยผ่านคุณสมบัติของคอมโพเนนต์ ในตัวอย่างด้านล่าง ระบบจะส่งองค์ประกอบ React ไปยังอุปกรณ์ประกอบฉาก headerChildren
และ sectionChildren
const MyNewsArticle = () => {
return (
<MyArticle
headerChildren={<h3>Extry, Extry! Read all about it!</h3>}
sectionChildren={<p>Children are props in React!</p>}
/>
);
};
ใน Lit ระบบจะกำหนดช่องโฆษณาย่อยโดยใช้แอตทริบิวต์ slot
@customElement("my-news-article")
export class MyNewsArticle extends LitElement {
render() {
return html`
<my-article>
<h3 slot="headerChildren">
Extry, Extry! Read all about it!
</h3>
<p slot="sectionChildren">
Children are composed with slots in Lit!
</p>
</my-article>
`;
}
}
หากไม่มีช่องเริ่มต้น (เช่น <slot>
) และไม่มีช่องที่มีแอตทริบิวต์ name
(เช่น <slot name="foo">
) ที่ตรงกับแอตทริบิวต์ slot
ของรายการย่อยขององค์ประกอบที่กำหนดเอง (เช่น <div slot="foo">
) โหนดนั้นจะไม่ถูกคาดการณ์และจะไม่แสดง
9. การอ้างอิง
ในบางครั้ง นักพัฒนาซอฟต์แวร์อาจจำเป็นต้องเข้าถึง API ของ HTMLElement
ในส่วนนี้ คุณจะได้เรียนรู้วิธีรับการอ้างอิงองค์ประกอบใน Lit
ข้อมูลอ้างอิงของรีแอ็กชัน
คอมโพเนนต์รีแอ็กชันจะเปลี่ยนเป็นชุดการเรียกใช้ฟังก์ชันที่สร้าง DOM เสมือนเมื่อเรียกใช้ DOM เสมือนนี้ตีความโดย ReactDOM และแสดงผล HTMLElements
ใน React นั้น Refs จะมีพื้นที่ว่างในหน่วยความจำสำหรับใส่ HTMLElement ที่สร้างขึ้น
const RefsExample = (props) => {
const inputRef = React.useRef(null);
const onButtonClick = React.useCallback(() => {
inputRef.current?.focus();
}, [inputRef]);
return (
<div>
<input type={"text"} ref={inputRef} />
<br />
<button onClick={onButtonClick}>
Click to focus on the input above!
</button>
</div>
);
};
ในตัวอย่างข้างต้น คอมโพเนนต์ React จะทำสิ่งต่อไปนี้
- แสดงผลการป้อนข้อความว่างเปล่าและปุ่มที่มีข้อความ
- โฟกัสอินพุตเมื่อคลิกปุ่ม
หลังจากการแสดงภาพครั้งแรก React จะตั้งค่า inputRef.current
เป็น HTMLInputElement
ที่สร้างขึ้นผ่านแอตทริบิวต์ ref
Lit "การอ้างอิง" กับ @query
Lit อยู่ใกล้เบราว์เซอร์และสร้างแอบสแตรกต์ที่บางมากเหนือฟีเจอร์ในเบราว์เซอร์
ความรู้สึกที่เทียบเท่ากับ refs
ใน Lit คือ HTMLElement ที่นักตกแต่ง @query
และ @queryAll
แสดงผล
@customElement("my-element")
export class MyElement extends LitElement {
@query('input') // Define the query
inputEl!: HTMLInputElement; // Declare the prop
// Declare the click event listener
onButtonClick() {
// Use the query to focus
this.inputEl.focus();
}
render() {
return html`
<input type="text">
<br />
<!-- Bind the click listener -->
<button @click=${this.onButtonClick}>
Click to focus on the input above!
</button>
`;
}
}
ในตัวอย่างข้างต้น คอมโพเนนต์ Lit มีหน้าที่ต่อไปนี้
- กำหนดพร็อพเพอร์ตี้บน
MyElement
โดยใช้เครื่องมือตกแต่ง@query
(การสร้าง Getter สำหรับHTMLInputElement
) - ประกาศและแนบ Callback ของเหตุการณ์การคลิกที่เรียกว่า
onButtonClick
- โฟกัสการป้อนข้อมูลเมื่อคลิกปุ่ม
ใน JavaScript ตัวตกแต่ง @query
และ @queryAll
จะทำงาน querySelector
และ querySelectorAll
ตามลำดับ นี่คือ JavaScript ที่เทียบเท่ากับ @query('input') inputEl!: HTMLInputElement;
get inputEl() {
return this.renderRoot.querySelector('input');
}
หลังจากที่คอมโพเนนต์ Lit ยืนยันเทมเพลตของเมธอด render
ไปยังรูทของ my-element
แล้ว เครื่องมือตกแต่ง @query
จะอนุญาตให้ inputEl
แสดงผลองค์ประกอบ input
รายการแรกที่พบในรูทของการแสดงผล จะแสดงผล null
หาก @query
ไม่พบองค์ประกอบที่ระบุ
หากมีเอลิเมนต์ input
หลายรายการในรูทของการแสดงผล @queryAll
จะแสดงผลรายการโหนด
10. สถานะสื่อกลาง
ในส่วนนี้ คุณจะได้รู้วิธีเป็นสื่อกลางสถานะระหว่างคอมโพเนนต์ใน Lit
ส่วนประกอบที่นำกลับมาใช้ใหม่ได้
แสดงความรู้สึกเลียนแบบไปป์ไลน์การแสดงผลที่ทำงานด้วยโฟลว์ข้อมูลจากด้านบนลงล่าง ผู้ปกครองช่วยจัดหารัฐให้แก่เด็กๆ ผ่านอุปกรณ์ประกอบฉาก เด็กสื่อสารกับผู้ปกครองผ่านการเรียกกลับที่พบในอุปกรณ์ประกอบฉาก
const CounterButton = (props) => {
const label = props.step < 0
? `- ${-1 * props.step}`
: `+ ${props.step}`;
return (
<button
onClick={() =>
props.addToCounter(props.step)}>{label}</button>
);
};
ในตัวอย่างข้างต้น คอมโพเนนต์ React จะทำสิ่งต่อไปนี้
- สร้างป้ายกำกับโดยอิงตามค่า
props.step
- แสดงผลปุ่มที่มี +step หรือ -step เป็นป้ายกำกับ
- อัปเดตคอมโพเนนต์หลักโดยการเรียกใช้
props.addToCounter
ด้วยprops.step
เป็นอาร์กิวเมนต์เมื่อคลิก
แม้ว่าจะสามารถส่ง Callback ใน Lit ได้ แต่รูปแบบปกติจะแตกต่างออกไป คอมโพเนนต์รีแอ็กชันในตัวอย่างด้านบนอาจเขียนเป็นองค์ประกอบ Lit ในตัวอย่างด้านล่าง
@customElement('counter-button')
export class CounterButton extends LitElement {
@property({type: Number}) step: number = 0;
onClick() {
const event = new CustomEvent('update-counter', {
bubbles: true,
detail: {
step: this.step,
}
});
this.dispatchEvent(event);
}
render() {
const label = this.step < 0
? `- ${-1 * this.step}` // "- 1"
: `+ ${this.step}`; // "+ 1"
return html`
<button @click=${this.onClick}>${label}</button>
`;
}
}
ในตัวอย่างด้านบน คอมโพเนนต์ Lit จะทำงานดังต่อไปนี้
- สร้างพร็อพเพอร์ตี้เชิงรับ
step
- ส่งเหตุการณ์ที่กำหนดเองชื่อ
update-counter
ที่มีค่าstep
ขององค์ประกอบเมื่อคลิก
เหตุการณ์ของเบราว์เซอร์จะปรากฏขึ้นตั้งแต่องค์ประกอบย่อยไปจนถึงองค์ประกอบหลัก เหตุการณ์ช่วยให้ผู้เผยแพร่โฆษณาย่อยสามารถออกอากาศเหตุการณ์การโต้ตอบและการเปลี่ยนแปลงสถานะ โดยพื้นฐานแล้ว React จะส่งสถานะไปในทิศทางตรงกันข้าม ดังนั้นจึงไม่บ่อยนักที่จะเห็นการจ่ายคอมโพเนนต์ของรีแอ็กชันและฟังเหตุการณ์ในลักษณะเดียวกับคอมโพเนนต์ Lit
คอมโพเนนต์ที่เก็บสถานะ
ใน React นั้นเป็นเรื่องปกติที่จะใช้ฮุกเพื่อจัดการสถานะ คุณสร้างคอมโพเนนต์ MyCounter
ได้โดยใช้คอมโพเนนต์ CounterButton
ซ้ำ สังเกตวิธีที่มีการส่ง addToCounter
ไปยัง CounterButton
ทั้ง 2 อินสแตนซ์
const MyCounter = (props) => {
const [counterSum, setCounterSum] = React.useState(0);
const addToCounter = useCallback(
(step) => {
setCounterSum(counterSum + step);
},
[counterSum, setCounterSum]
);
return (
<div>
<h3>Σ: {counterSum}</h3>
<CounterButton
step={-1}
addToCounter={addToCounter} />
<CounterButton
step={1}
addToCounter={addToCounter} />
</div>
);
};
ตัวอย่างข้างต้นมีดังต่อไปนี้
- สร้างสถานะ
count
- สร้าง Callback ที่เพิ่มหมายเลขไปยังสถานะ
count
CounterButton
ใช้addToCounter
เพื่ออัปเดตcount
ภายในวันที่step
ทุกครั้งที่คลิก
สามารถติดตั้งใช้งาน MyCounter
ที่คล้ายกันได้ใน Lit โปรดสังเกตว่าจะไม่มีการส่ง addToCounter
ไปยัง counter-button
ระบบจะเชื่อมโยง Callback เป็น Listener เหตุการณ์ของเหตุการณ์ @update-counter
ในองค์ประกอบระดับบนสุดแทน
@customElement("my-counter")
export class MyCounter extends LitElement {
@property({type: Number}) count = 0;
addToCounter(e: CustomEvent<{step: number}>) {
// Get step from detail of event or via @query
this.count += e.detail.step;
}
render() {
return html`
<div @update-counter="${this.addToCounter}">
<h3>Σ ${this.count}</h3>
<counter-button step="-1"></counter-button>
<counter-button step="1"></counter-button>
</div>
`;
}
}
ตัวอย่างข้างต้นมีดังต่อไปนี้
- สร้างคุณสมบัติเชิงรับชื่อ
count
ซึ่งจะอัปเดตคอมโพเนนต์เมื่อมีการเปลี่ยนแปลงค่า - เชื่อมโยง Callback
addToCounter
กับ Listener เหตุการณ์@update-counter
- อัปเดต
count
ด้วยการเพิ่มค่าที่พบในdetail.step
ของเหตุการณ์update-counter
- ตั้งค่า
step
ของcounter-button
ผ่านแอตทริบิวต์step
เป็นเรื่องปกติที่จะใช้คุณสมบัติเชิงรับใน Lit เพื่อเผยแพร่การเปลี่ยนแปลงจากผู้ปกครองไปสู่เด็ก ในทำนองเดียวกัน คุณควรใช้ระบบเหตุการณ์ของเบราว์เซอร์เพื่อแสดงรายละเอียดจากด้านล่างขึ้นบน
แนวทางนี้เป็นไปตามแนวทางปฏิบัติแนะนำและทำตามเป้าหมายของ Lit ในการให้การสนับสนุนข้ามแพลตฟอร์มสำหรับคอมโพเนนต์ของเว็บ
11. การจัดรูปแบบ
ในส่วนนี้ คุณจะได้ดูข้อมูลเกี่ยวกับการจัดรูปแบบใน Lit
การจัดรูปแบบ
Lit นำเสนอหลากหลายวิธีในการจัดรูปแบบองค์ประกอบและโซลูชันในตัว
รูปแบบในบรรทัด
Lit รองรับรูปแบบอินไลน์และการเชื่อมโยงกับรูปแบบดังกล่าว
import {LitElement, html, css} from 'lit';
import {customElement} from 'lit/decorators.js';
@customElement('my-element')
class MyElement extends LitElement {
render() {
return html`
<div>
<h1 style="color:orange;">This text is orange</h1>
<h1 style="color:rebeccapurple;">This text is rebeccapurple</h1>
</div>
`;
}
}
ในตัวอย่างด้านบน มีส่วนหัว 2 หัวข้อแบบแทรกในบรรทัด
นำเข้าและเชื่อมโยงเส้นขอบจาก border-color.js
เข้ากับข้อความสีส้ม:
...
import borderColor from './border-color.js';
...
html`
...
<h1 style="color:orange;${borderColor}">This text is orange</h1>
...`
การต้องคำนวณสตริงรูปแบบทุกครั้งอาจน่ารำคาญเล็กน้อย Lit จึงเสนอคำสั่งเพื่อช่วยในเรื่องนี้
styleMap
คำสั่ง styleMap
ช่วยให้ใช้ JavaScript เพื่อตั้งค่ารูปแบบแทรกในบรรทัดได้ง่ายขึ้น เช่น
import {LitElement, html, css} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {styleMap} from 'lit/directives/style-map.js';
@customElement('my-element')
class MyElement extends LitElement {
@property({type: String})
color = '#000'
render() {
// Define the styleMap
const headerStyle = styleMap({
'border-color': this.color,
});
return html`
<div>
<h1
style="border-style:solid;
<!-- Use the styleMap -->
border-width:2px;${headerStyle}">
This div has a border color of ${this.color}
</h1>
<input
type="color"
@input=${e => (this.color = e.target.value)}
value="#000">
</div>
`;
}
}
ตัวอย่างข้างต้นมีดังต่อไปนี้
- แสดง
h1
พร้อมเส้นขอบและตัวเลือกสี - เปลี่ยน
border-color
เป็นค่าจากตัวเลือกสี
นอกจากนี้ยังมี styleMap
ที่ใช้ตั้งค่ารูปแบบของ h1
ด้วย styleMap
ใช้ไวยากรณ์ที่คล้ายกับไวยากรณ์การเชื่อมโยงแอตทริบิวต์ style
ของ React
CSSResult
วิธีที่แนะนำสำหรับการจัดรูปแบบคอมโพเนนต์คือการใช้ลิเทอรัลของเทมเพลตที่ติดแท็ก css
import {LitElement, html, css} from 'lit';
import {customElement} from 'lit/decorators.js';
const ORANGE = css`orange`;
@customElement('my-element')
class MyElement extends LitElement {
static styles = [
css`
#orange {
color: ${ORANGE};
}
#purple {
color: rebeccapurple;
}
`
];
render() {
return html`
<div>
<h1 id="orange">This text is orange</h1>
<h1 id="purple">This text is rebeccapurple</h1>
</div>
`;
}
}
ตัวอย่างข้างต้นมีดังต่อไปนี้
- ประกาศเทมเพลตที่ติดแท็ก CSS ที่มีการเชื่อมโยง
- ตั้งค่าสีของ
h1
2 สีด้วยรหัส
ประโยชน์ของการใช้แท็กเทมเพลต css
- แยกวิเคราะห์ 1 ครั้งต่อคลาสเทียบกับต่ออินสแตนซ์
- ติดตั้งใช้งานโดยคํานึงถึงความสามารถในการนําไปใช้ซ้ำได้ของโมดูล
- สามารถแยกสไตล์เป็นไฟล์ของตัวเองได้อย่างง่ายดาย
- ใช้งานร่วมกับ Polyfill ของคุณสมบัติที่กำหนดเอง CSS ได้
นอกจากนี้ โปรดสังเกตแท็ก <style>
ใน index.html
ดังนี้
<!-- index.html -->
<style>
h1 {
color: red !important;
}
</style>
Lit จะกำหนดขอบเขตคอมโพเนนต์ ให้กับรากเหง้า ซึ่งหมายความว่าสไตล์จะไม่หลุดหายไป ทีม Lit แนะนำให้ใช้พร็อพเพอร์ตี้ที่กำหนดเองของ CSS เพื่อส่งสไตล์ไปยังคอมโพเนนต์ต่างๆ เนื่องจากสามารถเจาะขอบเขตระดับ Lit ได้
แท็กรูปแบบ
นอกจากนี้ คุณยังแทรกแท็ก <style>
ในบรรทัดในเทมเพลตได้ด้วย เบราว์เซอร์จะกรองแท็กรูปแบบที่ซ้ำกันออก แต่การวางไว้ในเทมเพลตจะเป็นการแยกวิเคราะห์แท็กตามอินสแตนซ์ของคอมโพเนนต์ ไม่ใช่ต่อคลาส ตามในกรณีของเทมเพลตที่ติดแท็ก css
นอกจากนี้ การกรองข้อมูล CSSResult
ที่ซ้ำกันออกในเบราว์เซอร์ยังทำได้เร็วกว่ามากด้วย
แท็กลิงก์
การใช้ <link rel="stylesheet">
ในเทมเพลตก็มีโอกาสที่จะใช้สไตล์เช่นกัน แต่เราไม่แนะนำให้ใช้วิธีนี้เนื่องจากอาจทำให้เนื้อหาที่ไม่มีการจัดรูปแบบ (FOUC) กะพริบในตอนแรก
12. หัวข้อขั้นสูง (ไม่บังคับ)
JSX และ เทมเพลท
ลิขสิทธิ์และ DOM เสมือน
Lit-html ไม่มี DOM เสมือนแบบทั่วไปที่จำแนกแต่ละโหนด แต่กลับใช้ฟีเจอร์ด้านประสิทธิภาพตามข้อมูลจำเพาะของเทมเพลตที่ติดแท็กของ ES2015 ลิเทอรัลเทมเพลตติดแท็กเป็นสตริงลิเทอรัลของเทมเพลตที่มีฟังก์ชันแท็กแนบอยู่
ตัวอย่างลิเทอรัลของเทมเพลตมีดังนี้
const str = 'string';
console.log(`This is a template literal ${str}`);
ตัวอย่างลิเทอรัลของเทมเพลตที่ติดแท็กมีดังนี้
const tag = (strings, ...values) => ({strings, values});
const f = (x) => tag`hello ${x} how are you`;
console.log(f('world')); // {strings: ["hello ", " how are you"], values: ["world"]}
console.log(f('world').strings === f(1 + 2).strings); // true
ในตัวอย่างข้างต้น แท็กคือฟังก์ชัน tag
และฟังก์ชัน f
จะแสดงผลการเรียกใช้ลิเทอรัลของเทมเพลตที่ติดแท็ก
ความมหัศจรรย์ของประสิทธิภาพจำนวนมากใน Lit มาจากข้อเท็จจริงที่ว่าอาร์เรย์สตริงที่ส่งผ่านไปยังฟังก์ชันแท็กมีตัวชี้เดียวกัน (ดังที่แสดงใน console.log
ที่สอง) เบราว์เซอร์จะไม่สร้างอาร์เรย์ strings
ใหม่ต่อการเรียกใช้ฟังก์ชันแท็กแต่ละรายการ เนื่องจากมีการใช้ลิเทอรัลเทมเพลตเดียวกัน (กล่าวคือ อยู่ในตำแหน่งเดียวกันใน AST) ดังนั้น การเชื่อมโยง การแยกวิเคราะห์ และการแคชเทมเพลตของ Lit สามารถใช้ประโยชน์จากฟีเจอร์เหล่านี้โดยไม่ต้องเพิ่มค่าใช้จ่ายในรันไทม์มากนัก
ลักษณะการทำงานในเบราว์เซอร์ในตัวของเทมเพลตที่ติดแท็กนี้ทำให้ Lit ได้เปรียบด้านประสิทธิภาพอย่างมาก DOM เสมือนแบบดั้งเดิมส่วนใหญ่จะทำงานส่วนใหญ่ใน JavaScript แต่จริงๆ แล้วเทมเพลตที่ติดแท็กมีลักษณะการทำงานส่วนใหญ่ที่แตกต่างกันใน C++ ของเบราว์เซอร์
หากต้องการเริ่มใช้เทมเพลตที่ติดแท็ก HTML แบบตรงตัวกับ React หรือ Preact ทีม Lit ขอแนะนำไลบรารี htm
แม้ว่าเว็บไซต์ Google Codelabs และตัวแก้ไขโค้ดออนไลน์หลายรายจะมีกรณีเดียวกัน แต่คุณจะพบว่าการไฮไลต์ไวยากรณ์ตามตัวอักษรของเทมเพลตที่ติดแท็กนั้นไม่ใช่รูปแบบที่พบได้บ่อยนัก IDE และเครื่องมือแก้ไขข้อความบางรายการรองรับอุปกรณ์โดยค่าเริ่มต้น เช่น Atom และเครื่องมือไฮไลต์ Codeblock ของ GitHub นอกจากนี้ ทีม Lit ยังทำงานอย่างใกล้ชิดกับชุมชนเพื่อดูแลรักษาโปรเจ็กต์ต่างๆ เช่น lit-plugin
ซึ่งเป็นปลั๊กอิน VS Code ที่จะเพิ่มการไฮไลต์ไวยากรณ์ การตรวจสอบประเภท และหลักสติปัญญาให้กับโปรเจ็กต์ Lit ของคุณ
ลิขสิทธิ์และ JSX + React DOM
JSX ไม่ได้ทำงานในเบราว์เซอร์และใช้ตัวประมวลผลล่วงหน้าเพื่อแปลง JSX เป็นการเรียกใช้ฟังก์ชัน JavaScript แทน (โดยปกติจะผ่าน Babel)
ตัวอย่างเช่น Babel จะเปลี่ยนแปลงสิ่งนี้:
const element = <div className="title">Hello World!</div>;
ReactDOM.render(element, mountNode);
มาพูดถึงกัน:
const element = React.createElement('div', {className: 'title'}, 'Hello World!');
ReactDOM.render(element, mountNode);
จากนั้น React DOM จะนำเอาต์พุต React ไปแปลงเป็น DOM จริง ซึ่งได้แก่ พร็อพเพอร์ตี้, แอตทริบิวต์, Listener เหตุการณ์ และทั้งหมด
Lit-html ใช้ลิเทอรัลของเทมเพลตที่ติดแท็ก ซึ่งสามารถทำงานในเบราว์เซอร์โดยไม่ต้องเปลี่ยนรูปแบบหรือใช้โปรแกรมประมวลผลล่วงหน้า ซึ่งหมายความว่าหากต้องการเริ่มต้นใช้งาน Lit สิ่งที่คุณต้องมีคือไฟล์ HTML, สคริปต์โมดูล ES และเซิร์ฟเวอร์ นี่คือสคริปต์ที่เรียกใช้เบราว์เซอร์ได้ทั้งหมด:
<!DOCTYPE html>
<html>
<head>
<script type="module">
import {html, render} from 'https://cdn.skypack.dev/lit';
render(
html`<div>Hello World!</div>`,
document.querySelector('.root')
)
</script>
</head>
<body>
<div class="root"></div>
</body>
</html>
นอกจากนี้ เนื่องจากระบบเทมเพลตของ Lit ที่ชื่อ lit-html ไม่ได้ใช้ DOM เสมือนแบบทั่วไป แต่ใช้ DOM API โดยตรง ขนาดของ Lit 2 จึงลดขนาดลงและใช้ gzip น้อยกว่า 5 KB เมื่อเทียบกับ React (2.8kb) + record-dom's (39.4kb) 40kb ที่ถูกลดขนาดและใช้ gizp
กิจกรรม
React ใช้ระบบเหตุการณ์สังเคราะห์ ซึ่งหมายความว่า Active-dom ต้องกำหนดทุกเหตุการณ์ที่จะใช้กับคอมโพเนนต์ทั้งหมด และให้ Listener เหตุการณ์แบบ CamlCase สำหรับโหนดแต่ละประเภท ด้วยเหตุนี้ JSX จึงไม่มีเมธอดในการกำหนด Listener เหตุการณ์สำหรับเหตุการณ์ที่กำหนดเองและนักพัฒนาซอฟต์แวร์ต้องใช้ ref
จากนั้นจึงใช้ Listener อย่างจริงจัง การทำเช่นนี้ทำให้นักพัฒนาแอปได้รับประสบการณ์การใช้งานในระดับต่ำกว่าเดิมเมื่อผสานรวมไลบรารีที่ไม่มี React อยู่แล้ว ซึ่งทำให้ต้องเขียน Wrapper ที่เจาะจงของ React
Lit-html เข้าถึง DOM โดยตรงและใช้เหตุการณ์แบบเนทีฟ ดังนั้นการเพิ่ม Listener เหตุการณ์จึงง่ายเหมือน @event-name=${eventNameListener}
ซึ่งหมายความว่าการแยกวิเคราะห์รันไทม์จะน้อยลงสําหรับการเพิ่ม Listener เหตุการณ์และการเริ่มทํางาน
ส่วนประกอบและ ของตกแต่ง
แสดงความรู้สึกและ องค์ประกอบที่กำหนดเอง
ในเบื้องหลัง LitElement จะใช้องค์ประกอบที่กำหนดเองเพื่อรวมคอมโพเนนต์ต่างๆ องค์ประกอบที่กำหนดเองทำให้เกิดข้อดีข้อเสียบางอย่างระหว่างคอมโพเนนต์ React เมื่อพูดถึงการแยกคอมโพเนนต์ (พูดถึงสถานะและวงจรชีวิตเพิ่มเติมในส่วนสถานะและวงจร)
ระบบคอมโพเนนต์มีข้อดีที่องค์ประกอบที่กำหนดเองบางอย่างมี
- มาพร้อมเบราว์เซอร์และไม่ต้องใช้เครื่องมือใดๆ
- ปรับให้พอดีกับทุก API ของเบราว์เซอร์ตั้งแต่
innerHTML
และdocument.createElement
ไปจนถึงquerySelector
- โดยทั่วไปแล้วจะใช้ได้ในกรอบการทำงานต่างๆ
- ลงทะเบียนแบบ Lazy Loading กับ
customElements.define
และ "hydrate" ได้ DOM
องค์ประกอบที่กำหนดเองมีข้อเสียบางประการเมื่อเทียบกับคอมโพเนนต์ React ดังนี้
- ไม่สามารถสร้างองค์ประกอบที่กำหนดเองโดยไม่กำหนดคลาส (จึงไม่มีคอมโพเนนต์การทำงานแบบ JSX)
- ต้องมีแท็กปิด
- หมายเหตุ: แม้ว่าผู้ให้บริการเบราว์เซอร์อำนวยความสะดวกของนักพัฒนาซอฟต์แวร์มักจะเสียใจกับข้อกำหนดของแท็กที่ปิดตัวเองอยู่เสมอ ด้วยเหตุนี้ข้อกำหนดใหม่ๆ จึงไม่มีแท็กที่ปิดเอง
- นำโหนดเพิ่มเติมเข้ามาในแผนผัง DOM ซึ่งอาจทำให้เกิดปัญหาในการออกแบบ
- ต้องลงทะเบียนผ่าน JavaScript
Lit เลือกใช้องค์ประกอบที่กำหนดเองมากกว่าระบบองค์ประกอบที่กำหนดเอง เนื่องจากองค์ประกอบที่กำหนดเองนั้นติดตั้งในตัวเบราว์เซอร์ และทีม Lit เชื่อว่าข้อดีของการทำงานข้ามเฟรมนั้นสำคัญกว่าประโยชน์ที่ได้รับจากเลเยอร์นามธรรมคอมโพเนนต์ อันที่จริงแล้ว ความพยายามของทีม Lit ในเรื่อง AI ได้เอาชนะปัญหาหลักๆ ในการลงทะเบียน JavaScript ได้ นอกจากนี้ บริษัทบางแห่ง เช่น GitHub ใช้ประโยชน์จากการลงทะเบียนแบบ Lazy Loading ขององค์ประกอบที่กำหนดเอง เพื่อเพิ่มประสิทธิภาพให้หน้าเว็บอย่างต่อเนื่องโดยให้มีลูกเล่นเพิ่มเติม
ส่งข้อมูลไปยังองค์ประกอบที่กำหนดเอง
สิ่งที่คนมักเข้าใจผิดเกี่ยวกับองค์ประกอบที่กำหนดเองก็คือ ข้อมูลจะส่งเป็นสตริงได้เท่านั้น ความเข้าใจผิดนี้อาจมาจากข้อเท็จจริงที่ว่าแอตทริบิวต์ขององค์ประกอบจะเขียนเป็นสตริงได้เท่านั้น แม้จะเป็นจริงว่า Lit จะแคสต์แอตทริบิวต์สตริงไปยังประเภทที่กำหนดไว้ แต่องค์ประกอบที่กำหนดเองยังยอมรับข้อมูลที่ซับซ้อนเป็นพร็อพเพอร์ตี้ได้ด้วย
ตัวอย่างเช่น เมื่อมีคำจำกัดความ LitElement ต่อไปนี้
// data-test.ts
import {LitElement, html} from 'lit';
import {customElement, property} from 'lit/decorators.js';
@customElement('data-test')
class DataTest extends LitElement {
@property({type: Number})
num = 0;
@property({attribute: false})
data = {a: 0, b: null, c: [html`<div>hello</div>`, html`<div>world</div>`]}
render() {
return html`
<div>num + 1 = ${this.num + 1}</div>
<div>data.a = ${this.data.a}</div>
<div>data.b = ${this.data.b}</div>
<div>data.c = ${this.data.c}</div>`;
}
}
มีการกำหนดพร็อพเพอร์ตี้เชิงรับพื้นฐาน num
ซึ่งจะแปลงค่าสตริงของแอตทริบิวต์เป็น number
จากนั้นจึงเปิดตัวโครงสร้างข้อมูลที่ซับซ้อนด้วย attribute:false
ซึ่งปิดใช้งานการจัดการแอตทริบิวต์ของ Lit
วิธีส่งข้อมูลไปยังองค์ประกอบที่กำหนดเองมีดังนี้
<head>
<script type="module">
import './data-test.js'; // loads element definition
import {html} from './data-test.js';
const el = document.querySelector('data-test');
el.data = {
a: 5,
b: null,
c: [html`<div>foo</div>`,html`<div>bar</div>`]
};
</script>
</head>
<body>
<data-test num="5"></data-test>
</body>
รัฐและ วงจร
Callback ของ React Lifecycle อื่นๆ
static getDerivedStateFromProps
ไม่มีสิ่งที่เทียบเท่าใน Lit เนื่องจากพร็อพและสถานะเป็นพร็อพเพอร์ตี้คลาสเดียวกัน
shouldComponentUpdate
- ลิทเทียบเท่า
shouldUpdate
- เรียกใช้เมื่อแสดงผลครั้งแรกซึ่งต่างจาก React
- ฟังก์ชันคล้ายกับ
shouldComponentUpdate
ของ React
getSnapshotBeforeUpdate
ใน Lit จะมี getSnapshotBeforeUpdate
คล้ายกับทั้ง update
และ willUpdate
willUpdate
- โทรก่อน
update
- สิ่งที่ต่างจาก
getSnapshotBeforeUpdate
คือจะมีการเรียกwillUpdate
ก่อนrender
- การเปลี่ยนแปลงพร็อพเพอร์ตี้เชิงรับใน
willUpdate
จะไม่ทริกเกอร์รอบการอัปเดตอีกครั้ง - เป็นตำแหน่งที่ดีในการคำนวณค่าพร็อพเพอร์ตี้ที่ขึ้นอยู่กับพร็อพเพอร์ตี้อื่นๆ และใช้ในขั้นตอนการอัปเดตที่เหลือ
- วิธีนี้เรียกใช้บนเซิร์ฟเวอร์ใน SSR จึงไม่แนะนำให้เข้าถึง DOM ที่นี่
update
- โทรหลัง
willUpdate
- สิ่งที่ต่างจาก
getSnapshotBeforeUpdate
คือจะมีการเรียกupdate
ก่อนrender
- การเปลี่ยนแปลงพร็อพเพอร์ตี้เชิงรับใน
update
จะไม่ทริกเกอร์รอบการอัปเดตอีกครั้งหากมีการเปลี่ยนแปลงก่อนการเรียกใช้super.update
- เป็นตำแหน่งที่ดีในการบันทึกข้อมูลจาก DOM รอบๆ คอมโพเนนต์ก่อนที่เอาต์พุตที่แสดงผลจะผูกเข้ากับ DOM
- ระบบไม่เรียกใช้เมธอดนี้บนเซิร์ฟเวอร์ใน SSR
Callback ของ Lit Lifecycle อื่นๆ
มี Callback ของวงจรหลายรายการที่ไม่ได้กล่าวถึงในส่วนก่อนหน้าเพราะไม่มีสิ่งที่คล้ายกันใน React ปัจจัยต่างๆ มีดังนี้
attributeChangedCallback
จะมีการเรียกใช้เมื่อ observedAttributes
ขององค์ประกอบรายการใดรายการหนึ่งมีการเปลี่ยนแปลง ทั้ง observedAttributes
และ attributeChangedCallback
เป็นส่วนหนึ่งของข้อกำหนดขององค์ประกอบที่กำหนดเอง และมีการใช้งานโดย Lit ขั้นสูงเพื่อมอบ API แอตทริบิวต์สำหรับองค์ประกอบ Lit
adoptedCallback
เรียกใช้เมื่อย้ายคอมโพเนนต์ไปยังเอกสารใหม่ เช่น จาก documentFragment
ของ HTMLTemplateElement
ไปยัง document
หลัก Callback นี้ยังเป็นส่วนหนึ่งของข้อกำหนดขององค์ประกอบที่กำหนดเอง และควรใช้สำหรับ Use Case ขั้นสูงเมื่อคอมโพเนนต์เปลี่ยนแปลงเอกสารเท่านั้น
พร็อพเพอร์ตี้และวิธีการใช้วงจรอื่นๆ
เมธอดและพร็อพเพอร์ตี้เหล่านี้จะเป็นสมาชิกชั้นเรียนที่คุณสามารถเรียกใช้ ลบล้าง หรือรอเพื่อช่วยจัดการกระบวนการในวงจรได้
updateComplete
นี่คือ Promise
ที่จะแก้ไขเมื่อองค์ประกอบอัปเดตเสร็จแล้ว เนื่องจากวงจรการอัปเดตและแสดงผลเป็นแบบไม่พร้อมกัน ตัวอย่าง
async nextButtonClicked() {
this.step++;
// Wait for the next "step" state to render
await this.updateComplete;
this.dispatchEvent(new Event('step-rendered'));
}
getUpdateComplete
นี่เป็นเมธอดที่ควรลบล้างเพื่อปรับแต่งเมื่อ updateComplete
แปลงค่า กรณีนี้พบได้บ่อยเมื่อคอมโพเนนต์แสดงผลคอมโพเนนต์ย่อยและรอบการแสดงผลต้องซิงค์กัน เช่น
class MyElement extends LitElement {
...
async getUpdateComplete() {
await super.getUpdateComplete();
await this.myChild.updateComplete;
}
}
performUpdate
วิธีนี้คือสิ่งที่เรียก Callback ของวงจรการอัปเดต ซึ่งโดยทั่วไปจะไม่จำเป็นต้องดำเนินการ ยกเว้นในบางกรณีที่ต้องทำการอัปเดตพร้อมกันหรือสำหรับการตั้งเวลาที่กำหนดเอง ซึ่งพบได้ไม่บ่อยนัก
hasUpdated
พร็อพเพอร์ตี้นี้จะเป็น true
หากคอมโพเนนต์มีการอัปเดตอย่างน้อย 1 ครั้ง
isConnected
ส่วนหนึ่งของข้อมูลจำเพาะขององค์ประกอบที่กำหนดเอง พร็อพเพอร์ตี้นี้จะเป็น true
หากองค์ประกอบแนบอยู่กับแผนผังเอกสารหลักในขณะนี้
การแสดงภาพวงจรการอัปเดต Lit อัปเดต
วงจรการอัปเดตประกอบด้วย 3 ส่วน ดังนี้
- ก่อนการอัปเดต
- อัปเดต
- หลังการอัปเดต
ก่อนอัปเดต
หลังจากวันที่ requestUpdate
ระบบจะรอการอัปเดตที่กําหนดเวลาไว้
อัปเดต
หลังการอัปเดต
ฮุก
ทำไมต้องฮุก
มีการนำฮุกเข้าสู่ React สำหรับ Use Case ของคอมโพเนนต์ฟังก์ชันอย่างง่ายที่ต้องระบุสถานะ ในหลายกรณี คอมโพเนนต์ฟังก์ชันที่มีฮุกมักจะเรียบง่ายและอ่านง่ายกว่าคอมโพเนนต์ประเภทคอมโพเนนต์คลาส อย่างไรก็ตาม เมื่อมีการอัปเดตสถานะแบบไม่พร้อมกันและส่งข้อมูลระหว่างฮุกหรือเอฟเฟกต์ รูปแบบ Hook มักจะไม่เพียงพอ และโซลูชันที่อิงตามคลาส เช่น เครื่องมือควบคุมเชิงรับก็มีแนวโน้มที่จะโดดเด่นออกมา
ฮุกคำขอ API และ คอนโทรลเลอร์
การเขียนฮุกที่ขอข้อมูลจาก API เป็นเรื่องปกติ เช่น ลองดูคอมโพเนนต์ฟังก์ชันรีแอ็กชันนี้ซึ่งทำหน้าที่ต่อไปนี้
index.tsx
- แสดงผลข้อความ
- แสดงการตอบกลับของ
useAPI
- รหัสผู้ใช้ + ชื่อผู้ใช้
- ข้อความแสดงข้อผิดพลาด
- 404 เมื่อเข้าถึงผู้ใช้ 11 (ตามการออกแบบ)
- ล้มเลิกข้อผิดพลาดหากการดึงข้อมูล API ถูกล้มเลิก
- ข้อความการโหลด
- แสดงปุ่มการทำงาน
- ผู้ใช้รายถัดไป: ซึ่งดึงข้อมูล API สำหรับผู้ใช้รายถัดไป
- ยกเลิก: ซึ่งจะล้มเลิกการดึงข้อมูล API และแสดงข้อผิดพลาด
useApi.tsx
- กำหนดฮุกที่กำหนดเอง
useApi
- จะไม่ซิงค์ออบเจ็กต์ผู้ใช้จาก API หรือไม่
- การปล่อยก๊าซ:
- ชื่อผู้ใช้
- กำลังโหลดการดึงข้อมูลหรือไม่
- ข้อความแสดงข้อผิดพลาดที่ปรากฏ
- มีการเรียกกลับเพื่อล้มเลิกการดึงข้อมูล
- ล้มเลิกการดึงข้อมูลที่อยู่ระหว่างดำเนินการ
- กำหนดฮุกที่กำหนดเอง
ดูการติดตั้งใช้งาน Lit + ตัวควบคุมการโต้ตอบ
สรุปประเด็นสำคัญ:
- ตัวควบคุมเชิงรับมีลักษณะคล้ายกับฮุกที่กำหนดเองมากที่สุด
- การส่งข้อมูลที่แสดงผลไม่ได้ระหว่าง Callback และเอฟเฟกต์
- React ใช้
useRef
เพื่อส่งข้อมูลระหว่างuseEffect
ถึงuseCallback
- Lit ใช้พร็อพเพอร์ตี้ของชั้นเรียนส่วนตัว
- React เลียนแบบลักษณะการทำงานของพร็อพเพอร์ตี้ส่วนตัวของชั้นเรียน
- React ใช้
นอกจากนี้ หากคุณชอบไวยากรณ์ของคอมโพเนนต์ฟังก์ชัน React ที่มีฮุก แต่ใช้สภาพแวดล้อมแบบไร้บิลด์แบบเดียวกัน ทีม Lit ก็ขอแนะนำไลบรารี Haunted เป็นอย่างยิ่ง
เด็ก
ช่องเริ่มต้น
เมื่อไม่ได้รับแอตทริบิวต์ slot
องค์ประกอบ HTML ระบบจะกำหนดให้กับช่องที่ไม่มีชื่อซึ่งเป็นค่าเริ่มต้น ในตัวอย่างด้านล่าง MyApp
จะรวม 1 ย่อหน้าไว้ในช่องที่ตั้งชื่อ ย่อหน้าอื่นจะมีค่าเริ่มต้นเป็นช่องโฆษณาที่ไม่มีชื่อ"
@customElement("my-element")
export class MyElement extends LitElement {
render() {
return html`
<section>
<div>
<slot></slot>
</div>
<div>
<slot name="custom-slot"></slot>
</div>
</section>
`;
}
}
@customElement("my-app")
export class MyApp extends LitElement {
render() {
return html`
<my-element>
<p slot="custom-slot">
This paragraph will be placed in the custom-slot!
</p>
<p>
This paragraph will be placed in the unnamed default slot!
</p>
</my-element>
`;
}
}
ข้อมูลอัปเดตเกี่ยวกับสล็อต
เมื่อโครงสร้างองค์ประกอบสืบทอดของช่องมีการเปลี่ยนแปลง เหตุการณ์ slotchange
จะเริ่มทำงาน คอมโพเนนต์ Lit สามารถเชื่อมโยงผู้ฟังเหตุการณ์กับเหตุการณ์ slotchange
ในตัวอย่างด้านล่าง ช่องโฆษณาแรกที่พบใน shadowRoot
จะมี assignedNodes ในช่องแรกที่บันทึกลงในคอนโซลใน slotchange
@customElement("my-element")
export class MyElement extends LitElement {
onSlotChange(e: Event) {
const slot = this.shadowRoot.querySelector('slot');
console.log(slot.assignedNodes({flatten: true}));
}
render() {
return html`
<section>
<div>
<slot @slotchange="{this.onSlotChange}"></slot>
</div>
</section>
`;
}
}
อ้างอิง
การสร้างข้อมูลอ้างอิง
ทั้ง Lit และ React จะแสดงการอ้างอิงไปยัง HTMLElement หลังจากที่เรียกฟังก์ชัน render
แล้ว แต่คุณควรตรวจสอบวิธีที่ React และ Lit เขียน DOM ซึ่งส่งคืนภายหลังผ่านมัณฑนากรของ Lit @query
หรือ React Reference
React คือไปป์ไลน์ฟังก์ชันที่สร้าง React Components ไม่ใช่ HTMLElements เนื่องจากมีการประกาศการอ้างอิงก่อนที่จะแสดงผล HTMLElement ระบบจึงจัดสรรพื้นที่ในหน่วยความจำ นี่จึงเป็นเหตุผลที่ทำให้คุณเห็น null
เป็นค่าเริ่มต้นของการอ้างอิง เนื่องจากองค์ประกอบ DOM จริงยังไม่ได้สร้าง (หรือแสดงผล) เช่น useRef(null)
หลังจากที่ ReactDOM แปลงคอมโพเนนต์ React เป็น HTMLElement แล้ว ระบบจะมองหาแอตทริบิวต์ที่ชื่อว่า ref
ใน ReactComponent ReactDOM จะวางการอ้างอิงของ HTMLElement ไว้ที่ ref.current
หากมี
LitElement ใช้ฟังก์ชันแท็กเทมเพลต html
จาก lit-html เพื่อเขียนองค์ประกอบเทมเพลตภายในระบบ LitElement จะประทับเนื้อหาของเทมเพลตเป็น shadow DOM ขององค์ประกอบที่กำหนดเองหลังจากแสดงผล Shadow DOM เป็นแผนผัง DOM ที่มีขอบเขตขอบเขตที่ล้อมรอบด้วยรากของเงา จากนั้นเครื่องมือตกแต่ง @query
จะสร้าง Getter สำหรับพร็อพเพอร์ตี้ ซึ่งจะดำเนินการ this.shadowRoot.querySelector
ในรากที่กำหนดขอบเขต
ค้นหาองค์ประกอบหลายรายการ
ในตัวอย่างด้านล่าง เครื่องมือตกแต่ง @queryAll
จะแสดง 2 ย่อหน้าในรากส่วนเงาเป็น NodeList
@customElement("my-element")
export class MyElement extends LitElement {
@queryAll('p')
paragraphs!: NodeList;
render() {
return html`
<p>Hello, world!</p>
<p>How are you?</p>
`;
}
}
โดยพื้นฐานแล้ว @queryAll
จะสร้าง Getter สำหรับ paragraphs
ซึ่งแสดงผลผลลัพธ์ของ this.shadowRoot.querySelectorAll()
ใน JavaScript สามารถประกาศ Getter ให้ดำเนินการตามจุดประสงค์เดียวกันได้ ดังนี้
get paragraphs() {
return this.renderRoot.querySelectorAll('p');
}
องค์ประกอบที่เปลี่ยนแปลงในข้อความค้นหา
เครื่องมือตกแต่ง @queryAsync
เหมาะกับการจัดการโหนดที่เปลี่ยนแปลงได้ตามสถานะของพร็อพเพอร์ตี้ขององค์ประกอบอื่น
ในตัวอย่างด้านล่าง @queryAsync
จะค้นหาองค์ประกอบย่อหน้าแรก แต่องค์ประกอบย่อหน้าจะแสดงผลเมื่อ renderParagraph
สุ่มสร้างตัวเลขคี่เท่านั้น คำสั่ง @queryAsync
จะแสดงสัญญาที่จะได้รับการแก้ไขเมื่อย่อหน้าแรกพร้อมใช้งาน
@customElement("my-dissappearing-paragraph")
export class MyDisapppearingParagraph extends LitElement {
@queryAsync('p')
paragraph!: Promise<HTMLElement>;
renderParagraph() {
const randomNumber = Math.floor(Math.random() * 10)
if (randomNumber % 2 === 0) {
return "";
}
return html`<p>This checkbox is checked!`
}
render() {
return html`
${this.renderParagraph()}
`;
}
}
สถานะสื่อกลาง
ใน React นั้น กฎที่ใช้คือการใช้ Callback เนื่องจากมีสถานะเป็นสื่อกลางโดย React เอง คุณควรรีแอ็กที่จะไม่พึ่งสถานะที่องค์ประกอบจัดเตรียมไว้ DOM เป็นเพียงผลกระทบของกระบวนการแสดงผล
สถานะภายนอก
คุณใช้ Redux, MobX หรือไลบรารีการจัดการรัฐอื่นๆ ควบคู่ไปกับ Lit ได้
ระบบจะสร้างคอมโพเนนต์ Lit ในขอบเขตเบราว์เซอร์ ดังนั้นไลบรารีที่อยู่ในขอบเขตเบราว์เซอร์จะพร้อมใช้งานสำหรับ Lit ห้องสมุดอันน่าทึ่งจำนวนมากถูกสร้างขึ้นเพื่อใช้ระบบการจัดการของรัฐที่มีอยู่ใน Lit
นี่คือซีรีส์โดย Vaadin ที่อธิบายวิธีใช้ประโยชน์จาก Redux ในคอมโพเนนต์ Lit
ลองดู lit-mobx จาก Adobe เพื่อดูว่าเว็บไซต์ขนาดใหญ่สามารถใช้ประโยชน์จาก MobX ใน Lit ได้อย่างไร
นอกจากนี้ ไปที่ Apollo Elements เพื่อดูวิธีที่นักพัฒนาซอฟต์แวร์รวม GraphQL ในคอมโพเนนต์เว็บของตน
Lit ใช้งานได้กับฟีเจอร์ของเบราว์เซอร์ที่มาพร้อมเครื่อง และโซลูชันการจัดการสถานะส่วนใหญ่ในขอบเขตเบราว์เซอร์จะใช้ในคอมโพเนนต์ Lit ได้
การจัดรูปแบบ
Shadow DOM
ในการห่อหุ้มรูปแบบและ DOM ภายในองค์ประกอบที่กำหนดเอง Lit จะใช้ Shadow DOM Shadow Roots จะสร้างแผนผังเงาที่แยกจากแผนผังเอกสารหลัก ซึ่งหมายความว่ารูปแบบส่วนใหญ่จะอยู่ในเอกสารนี้ บางรูปแบบอาจหลุดผ่านไปได้ เช่น สีและสไตล์อื่นๆ ที่เกี่ยวข้องกับแบบอักษร
Shadow DOM ยังนำเสนอแนวคิดและตัวเลือกใหม่ๆ สำหรับข้อกำหนด CSS ดังต่อไปนี้ด้วย
:host, :host(:hover), :host([hover]) {
/* Styles the element in which the shadow root is attached to */
}
slot[name="title"]::slotted(*), slot::slotted(:hover), slot::slotted([hover]) {
/*
* Styles the elements projected into a slot element. NOTE: the spec only allows
* styling the direcly slotted elements. Children of those elements are not stylable.
*/
}
การแชร์รูปแบบ
Lit ทำให้การแชร์รูปแบบระหว่างคอมโพเนนต์ต่างๆ ในรูปแบบ CSSTemplateResults
ผ่านแท็กเทมเพลต css
เป็นเรื่องง่าย เช่น
// typography.ts
export const body1 = css`
.body1 {
...
}
`;
// my-el.ts
import {body1} from './typography.ts';
@customElement('my-el')
class MyEl Extends {
static get styles = [
body1,
css`/* local styles come after so they will override bod1 */`
]
render() {
return html`<div class="body1">...</div>`
}
}
ธีม
รากของเงาสร้างความท้าทายเล็กน้อยกับธีมแบบเดิมๆ ซึ่งโดยทั่วไปจะเป็นวิธีแท็กรูปแบบจากบนลงล่าง วิธีทั่วไปในการจัดการธีมด้วยคอมโพเนนต์ของเว็บที่ใช้ Shadow DOM คือการแสดง API รูปแบบผ่านคุณสมบัติที่กำหนดเองของ CSS ตัวอย่างเช่น นี่คือรูปแบบที่ดีไซน์ Material ใช้
.mdc-textfield-outline {
border-color: var(--mdc-theme-primary, /* default value */ #...);
}
.mdc-textfield--input {
caret-color: var(--mdc-theme-primary, #...);
}
จากนั้นผู้ใช้จะเปลี่ยนธีมของเว็บไซต์โดยใช้ค่าพร็อพเพอร์ตี้ที่กำหนดเองดังนี้
html {
--mdc-theme-primary: #F00;
}
html[dark] {
--mdc-theme-primary: #F88;
}
หากจำเป็นต้องกำหนดธีมจากด้านบนและคุณไม่สามารถเปิดเผยรูปแบบได้ คุณอาจปิดใช้ Shadow DOM ได้เสมอโดยการลบล้าง createRenderRoot
เพื่อแสดงผล this
ซึ่งจะแสดงผลคอมโพเนนต์ ลงในองค์ประกอบที่กำหนดเอง แทนที่จะเป็นรากเงาที่แนบอยู่กับองค์ประกอบที่กำหนดเอง คุณจะเสียรายละเอียดดังนี้ การห่อหุ้มรูปแบบ การห่อหุ้ม DOM และช่องโฆษณา
การผลิต
IE 11
หากคุณต้องการรองรับเบราว์เซอร์รุ่นเก่าอย่าง IE 11 คุณจะต้องโหลดโพลีฟิลบางส่วนซึ่งมีขนาดประมาณ 33 KB ดูข้อมูลเพิ่มเติมได้ที่นี่
กลุ่มแบบมีเงื่อนไข
ทีมงาน Lit แนะนำให้ใช้ 2 ชุด คือชุดหนึ่งสำหรับ IE 11 และอีกชุดสำหรับเบราว์เซอร์สมัยใหม่ ซึ่งมีประโยชน์หลายประการดังนี้
- การให้บริการ ES 6 จะรวดเร็วขึ้นและพร้อมให้บริการแก่ลูกค้าส่วนใหญ่ของคุณ
- Transpiled ES 5 ทำให้ขนาดแพ็กเกจเพิ่มขึ้นอย่างมาก
- แพ็กเกจแบบมีเงื่อนไขช่วยให้คุณได้รับสิ่งที่ดีที่สุดจากทั้ง 2 อย่าง
- รองรับ IE 11
- ไม่ทำงานช้าลงในเบราว์เซอร์สมัยใหม่
ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีสร้างแพ็กเกจที่ให้บริการอย่างมีเงื่อนไขได้ในเว็บไซต์เอกสารประกอบของเราที่นี่