1. Giới thiệu
Lit là gì
Lit là một thư viện đơn giản để tạo các thành phần web nhẹ và nhanh, hoạt động trong mọi khung hoặc không có khung. Với Lit, bạn có thể tạo các thành phần, ứng dụng, hệ thống thiết kế, v.v. có thể chia sẻ.
Kiến thức bạn sẽ học được
Cách chuyển đổi một số khái niệm về React sang Lit, chẳng hạn như:
- JSX và Tạo mẫu
- Thành phần và Đồ hoá trang
- Tiểu bang và Vòng đời
- Móc
- Thiếu nhi
- Tham chiếu
- Trạng thái dàn xếp
Sản phẩm bạn sẽ tạo ra
Vào cuối lớp học lập trình này, bạn sẽ có thể chuyển đổi các khái niệm thành phần React thành các thành phần tương tự Lit (Lit).
Bạn cần có
- Phiên bản mới nhất của Chrome, Safari, Firefox hoặc Edge.
- Có kiến thức về HTML, CSS, JavaScript và Công cụ của Chrome cho nhà phát triển.
- Kiến thức về React
- (Nâng cao) Nếu bạn muốn có trải nghiệm phát triển tốt nhất, hãy tải VS Code xuống. Bạn cũng sẽ cần lit-plugin cho VS Code và kháng nghị API.
2. Ánh sáng so với phản ứng
Các khái niệm và khả năng cốt lõi của Lit giống với các khái niệm và khả năng của React theo nhiều cách, nhưng Lit cũng có một số điểm khác biệt chính và khác biệt:
Quán nhỏ
Lit rất nhỏ: nó có kích thước khoảng 5kb được rút gọn và được nén so với React + DOM's 40+ kb.
Nhanh chóng
Trong các điểm chuẩn công khai so sánh hệ thống tạo mẫu của Lit, lit-html, với VDOM của React, lit-html xuất hiện nhanh hơn từ 8 đến 10% so với React trong trường hợp xấu nhất và nhanh hơn 50% trong các trường hợp sử dụng phổ biến hơn.
LitElement (lớp cơ sở thành phần của Lit) tăng mức hao tổn tối thiểu đối với lit-html, nhưng tăng hiệu suất của React khoảng 16-30% khi so sánh các tính năng của thành phần như mức sử dụng bộ nhớ và tương tác và thời gian khởi động.
Không cần bản dựng
Với các tính năng mới của trình duyệt như mô-đun ES và giá trị cố định mẫu được gắn thẻ, Lit không yêu cầu biên dịch để chạy. Điều này có nghĩa là bạn có thể thiết lập môi trường nhà phát triển bằng thẻ tập lệnh + trình duyệt + máy chủ, đồng thời thiết lập và chạy.
Với các mô-đun ES và CDN hiện đại như Skypack hoặc UNPKG, thậm chí có thể bạn sẽ không cần tôim để bắt đầu!
Tuy nhiên, nếu muốn, bạn vẫn có thể tạo và tối ưu hoá mã Lit. Việc hợp nhất các mô-đun ES gốc gần đây của nhà phát triển rất hiệu quả cho Lit – Lit chỉ là JavaScript thông thường và không cần CLI hoặc xử lý bản dựng dành riêng cho khung.
Không phụ thuộc vào khung
Các thành phần của Lit được xây dựng từ một bộ tiêu chuẩn web có tên là Thành phần web. Điều này có nghĩa là việc tạo một thành phần trong Lit sẽ hoạt động trong các khung hiện tại và trong tương lai. Nếu hệ thống hỗ trợ các phần tử HTML, thì API này cũng hỗ trợ các Thành phần web.
Vấn đề duy nhất với khả năng tương tác của khung là khi khung có hỗ trợ hạn chế cho DOM. React là một trong những khung này, nhưng nó cho phép thoát thông qua Refs và Refs trong React không phải là một trải nghiệm tốt cho nhà phát triển.
Nhóm Lit đang thực hiện một dự án thử nghiệm có tên là @lit-labs/react
. Dự án này sẽ tự động phân tích cú pháp các thành phần Lit của bạn và tạo một trình bao bọc React để bạn không phải sử dụng các ref.
Ngoài ra, Phần tử tuỳ chỉnh ở mọi nơi sẽ cho bạn biết khung và thư viện nào hoạt động tốt với các phần tử tuỳ chỉnh!
Hỗ trợ TypeScript hạng nhất
Mặc dù có thể viết tất cả mã Lit trong JavaScript, nhưng Lit được viết bằng TypeScript và nhóm Lit cũng khuyên các nhà phát triển nên sử dụng TypeScript!
Nhóm Lit đã làm việc với cộng đồng Lit để giúp duy trì các dự án đưa tính năng kiểm tra kiểu chữ và trí tuệ nhân tạo (Intellisense) của TypeScript vào mẫu Lit trong cả thời gian phát triển và xây dựng bằng lit-analyzer
và lit-plugin
.
Công cụ dành cho nhà phát triển được tích hợp vào trình duyệt
Thành phần văn bản chỉ là phần tử HTML trong DOM. Điều này có nghĩa là để kiểm tra các thành phần, bạn không cần phải cài đặt bất kỳ công cụ hoặc tập lệnh nào cho trình duyệt của mình.
Bạn chỉ cần mở các công cụ cho nhà phát triển, chọn một phần tử và khám phá các thuộc tính hoặc trạng thái của phần tử đó.
Công nghệ này được xây dựng với tính năng kết xuất phía máy chủ (SSR)
Lit 2 đã được xây dựng với sự hỗ trợ của SSR. Tại thời điểm viết lớp học lập trình này, nhóm Lit vẫn chưa phát hành các công cụ SSR ở dạng ổn định, nhưng nhóm Lit đã và đang triển khai các thành phần kết xuất phía máy chủ trên các sản phẩm của Google và đã thử nghiệm SSR trong các ứng dụng React. Nhóm Lit hy vọng sẽ sớm phát hành các công cụ này ra bên ngoài trên GitHub.
Trong thời gian chờ đợi, bạn có thể theo dõi tiến độ của nhóm Lit tại đây.
Cần có sự đồng ý thấp
Việc sử dụng Lit không đòi hỏi phải có cam kết đáng kể để sử dụng! Bạn có thể tạo các thành phần trong Lit và thêm các thành phần đó vào dự án hiện tại. Nếu không thích thì bạn không phải chuyển đổi toàn bộ ứng dụng cùng một lúc vì các thành phần web hoạt động trong các khung khác!
Bạn đã xây dựng toàn bộ ứng dụng trong Lit và muốn chuyển sang một ứng dụng khác? Sau đó, bạn có thể đặt ứng dụng Lit hiện tại vào trong khung mới và di chuyển nội dung mình muốn sang các thành phần của khung mới.
Ngoài ra, nhiều khung hiện đại hỗ trợ đầu ra trong các thành phần web. Điều đó có nghĩa là các khung này thường có thể vừa với phần tử Lit.
3. Thiết lập và khám phá Sân chơi
Có hai cách để thực hiện lớp học lập trình này:
- Bạn có thể hoàn toàn làm việc này trực tuyến, trong trình duyệt
- (Nâng cao) Bạn có thể thực hiện trên máy cục bộ bằng VS Code
Truy cập mã
Xuyên suốt lớp học lập trình này, sẽ có các đường liên kết đến sân chơi Lit như sau:
Playground là một hộp cát mã chạy hoàn toàn trong trình duyệt. Công cụ này có thể biên dịch và chạy các tệp TypeScript và JavaScript, đồng thời cũng có thể tự động phân giải các tệp nhập vào các mô-đun nút. ví dụ:
// before
import './my-file.js';
import 'lit';
// after
import './my-file.js';
import 'https://cdn.skypack.dev/lit';
Bạn có thể làm toàn bộ hướng dẫn trong sân chơi Lit, sử dụng các điểm kiểm tra này làm điểm xuất phát. Nếu đang sử dụng VS Code, bạn có thể sử dụng các điểm kiểm tra này để tải mã khởi động xuống cho bất kỳ bước nào, cũng như sử dụng chúng để kiểm tra công việc của bạn.
Khám phá giao diện người dùng sân chơi được chiếu sáng
Ảnh chụp màn hình giao diện người dùng Lit Playground nêu bật các phần mà bạn sẽ sử dụng trong lớp học lập trình này.
- Bộ chọn tệp. Ghi chú nút dấu cộng...
- Trình chỉnh sửa tệp.
- Xem trước mã.
- Nút tải lại.
- Nút Tải xuống.
Thiết lập mã VS (Nâng cao)
Dưới đây là những lợi ích khi sử dụng chế độ thiết lập Mã VS này:
- Kiểm tra loại mẫu
- Thông tin mẫu và tự động hoàn thành
Nếu bạn đã cài đặt TLD, VS Code (với trình bổ trợ lit-plugin) và biết cách sử dụng môi trường đó, bạn có thể chỉ cần tải xuống và khởi động các dự án này bằng cách thực hiện như sau:
- Nhấn vào nút tải xuống
- Giải nén nội dung của tệp tar vào một thư mục
- (Nếu TS) thiết lập tsconfig nhanh để xuất ra các mô-đun es và es2015+
- Cài đặt máy chủ nhà phát triển có thể phân giải các thông số mô-đun trần (nhóm Lit khuyên dùng @web/dev-server)
- Sau đây là ví dụ về
package.json
- Sau đây là ví dụ về
- Chạy máy chủ nhà phát triển và mở trình duyệt của bạn (nếu đang sử dụng @web/dev-server, bạn có thể dùng
npx web-dev-server --node-resolve --watch --open
)- Nếu bạn đang dùng ví dụ
package.json
, hãy sử dụngnpm run dev
- Nếu bạn đang dùng ví dụ
4. JSX và Tạo mẫu
Trong phần này, bạn sẽ tìm hiểu kiến thức cơ bản về việc tạo mẫu trong Lit.
JSX và Mẫu lit
JSX là một phần mở rộng cú pháp cho JavaScript, cho phép người dùng React dễ dàng viết mẫu bằng mã JavaScript của họ. Mẫu văn bản có mục đích tương tự: thể hiện giao diện người dùng của một thành phần dưới dạng một hàm của trạng thái.
Cú pháp cơ bản
Trong React, bạn sẽ kết xuất JSX hello world như sau:
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
);
Trong ví dụ trên, có hai phần tử và một "tên" đi kèm biến. In Lit, bạn sẽ làm như sau:
import {html, render} from 'lit';
const name = 'Josh Perez';
const element = html`
<h1>Hello, ${name}</h1>
<div>How are you?</div>`;
render(
element,
mountNode
);
Lưu ý rằng các mẫu Lit không cần Mảnh phản ứng để nhóm nhiều phần tử trong các mẫu của nó.
Trong Lit, các mẫu được bao bọc bằng một mẫu được gắn thẻ LITeral html
, đây chính là nơi Lit nhận được tên của nó!
Giá trị mẫu
Mẫu Lit có thể chấp nhận các mẫu Lit khác, được gọi là TemplateResult
. Ví dụ: gói name
trong thẻ in nghiêng (<i>
) và gói mã đó bằng một giá trị cố định của mẫu được gắn thẻ N.B. Nhớ sử dụng ký tự dấu phẩy ngược (`
) chứ không phải ký tự dấu nháy đơn ('
).
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
);
Văn bản TemplateResult
có thể chấp nhận các mảng, chuỗi, các TemplateResult
khác cũng như lệnh.
Để thực hiện một bài tập, hãy thử chuyển đổi đoạn mã React sau đây sang 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
);
Đáp số:
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
);
Đạo cụ chuyền bóng và bố trí dụng cụ
Một trong những khác biệt lớn nhất giữa cú pháp của JSX và Lit là cú pháp liên kết dữ liệu. Ví dụ: lấy đầu vào React này cùng với các liên kết:
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
);
Trong ví dụ trên, dữ liệu đầu vào được xác định, thực hiện như sau:
- Đặt thành một biến đã xác định bị vô hiệu hoá (false trong trường hợp này)
- Đặt lớp thành
static-class
kèm theo một biến (trong trường hợp này là"static-class my-class"
) - Đặt giá trị mặc định
In Lit, bạn sẽ làm như sau:
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
);
Trong ví dụ về Lit, một liên kết boolean được thêm vào để bật/tắt thuộc tính disabled
.
Tiếp theo, có một liên kết trực tiếp đến thuộc tính class
thay vì className
. Bạn có thể thêm nhiều liên kết vào thuộc tính class
, trừ phi bạn đang dùng lệnh classMap
. Đây là một trình trợ giúp khai báo để bật/tắt các lớp.
Cuối cùng, thuộc tính value
được đặt cho dữ liệu đầu vào. Không giống như trong React, thao tác này sẽ không đặt phần tử đầu vào thành chỉ đọc vì nó tuân theo cách triển khai và hành vi gốc của đầu vào.
Cú pháp liên kết tuyên bố văn bản
html`<my-element ?attribute-name=${booleanVar}>`;
- Tiền tố
?
là cú pháp liên kết để bật/tắt một thuộc tính trên một phần tử - Tương đương với
inputRef.toggleAttribute('attribute-name', booleanVar)
- Hữu ích cho các phần tử sử dụng
disabled
vìdisabled="false"
vẫn được DOM đọc là true vìinputElement.hasAttribute('disabled') === true
html`<my-element .property-name=${anyVar}>`;
- Tiền tố
.
là cú pháp liên kết để đặt thuộc tính của một phần tử - Tương đương với
inputRef.propertyName = anyVar
- Phù hợp để truyền dữ liệu phức tạp như đối tượng, mảng hoặc lớp
html`<my-element attribute-name=${stringVar}>`;
- Liên kết với thuộc tính của phần tử
- Tương đương với
inputRef.setAttribute('attribute-name', stringVar)
- Phù hợp với các giá trị cơ bản, bộ chọn quy tắc kiểu và querySelector
Trình xử lý truyền
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
);
Trong ví dụ trên, dữ liệu đầu vào được xác định, thực hiện như sau:
- Ghi nhật ký từ "nhấp chuột" khi người dùng nhấp vào thông tin đầu vào
- Ghi lại giá trị của dữ liệu đầu vào khi người dùng nhập một ký tự
In Lit, bạn sẽ làm như sau:
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
);
Trong ví dụ về Lit, có một trình nghe được thêm vào sự kiện click
bằng @click
.
Tiếp theo, thay vì sử dụng onChange
, sẽ có một liên kết với sự kiện input
gốc của <input>
vì sự kiện change
gốc chỉ kích hoạt trên blur
(Phản hồi thông tin tóm tắt trên các sự kiện này).
Cú pháp trình xử lý sự kiện phát trực tiếp
html`<my-element @event-name=${() => {...}}></my-element>`;
- Tiền tố
@
là cú pháp liên kết cho trình nghe sự kiện - Tương đương với
inputRef.addEventListener('event-name', ...)
- Sử dụng tên sự kiện DOM gốc
5. Thành phần và Đồ hoá trang
Trong phần này, bạn sẽ tìm hiểu về các thành phần và hàm của lớp Lit. Trạng thái và Hook sẽ được trình bày chi tiết hơn ở các phần sau.
Thành phần và lớp LitElement
Thành phần Lit tương đương với một thành phần của lớp React là LitElement và khái niệm của Lit về "thuộc tính phản ứng" là sự kết hợp giữa đạo cụ và trạng thái của React. Ví dụ:
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
);
Trong ví dụ trên, có một thành phần React:
- Hiển thị một
name
- Đặt giá trị mặc định của
name
thành chuỗi trống (""
) - Chỉ định lại
name
cho"Elliott"
Đây là cách bạn sẽ thực hiện việc này trong LitElement
Trong 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>`
}
}
Trong 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);
Và trong tệp HTML:
<!-- index.html -->
<head>
<script type="module" src="./index.js"></script>
</head>
<body>
<welcome-banner name="Elliott"></welcome-banner>
</body>
Đánh giá về những gì đang xảy ra trong ví dụ ở trên:
@property({type: String})
name = '';
- Xác định thuộc tính phản ứng công khai – một phần trong API công khai của thành phần
- Hiển thị một thuộc tính (theo mặc định) cũng như một thuộc tính trên thành phần của bạn
- Xác định cách chuyển đổi thuộc tính của thành phần (là các chuỗi) thành một giá trị
static get properties() {
return {
name: {type: String}
}
}
- Thư viện này có chức năng tương tự như trình trang trí TS
@property
nhưng chạy vốn trong JavaScript
render() {
return html`<h1>Hello, ${this.name}</h1>`
}
- Hàm này được gọi bất cứ khi nào thuộc tính phản ứng bị thay đổi
@customElement('welcome-banner')
class WelcomeBanner extends LitElement {
...
}
- Thao tác này liên kết tên thẻ Phần tử HTML với định nghĩa lớp
- Theo tiêu chuẩn Phần tử tuỳ chỉnh, tên thẻ phải chứa dấu gạch nối (-)
this
trong LitElement là bản sao của phần tử tuỳ chỉnh (trong trường hợp này là<welcome-banner>
)
customElements.define('welcome-banner', WelcomeBanner);
- Đây là JavaScript tương đương với trình trang trí
@customElement
TS
<head>
<script type="module" src="./index.js"></script>
</head>
- Nhập định nghĩa phần tử tuỳ chỉnh
<body>
<welcome-banner name="Elliott"></welcome-banner>
</body>
- Thêm phần tử tuỳ chỉnh vào trang
- Đặt thuộc tính
name
thành'Elliott'
Thành phần hàm
Lit không diễn giải 1:1 về thành phần hàm vì không sử dụng JSX hoặc bộ tiền xử lý. Tuy nhiên, việc soạn một hàm nhận thuộc tính và kết xuất DOM dựa trên các thuộc tính đó khá đơn giản. Ví dụ:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Elliott"/>
ReactDOM.render(
element,
mountNode
);
Lit, văn bản này sẽ là:
import {html, render} from 'lit';
function Welcome(props) {
return html`<h1>Hello, ${props.name}</h1>`;
}
render(
Welcome({name: 'Elliott'}),
document.body.querySelector('#root')
);
6. Tiểu bang và Vòng đời
Trong phần này, bạn sẽ tìm hiểu về trạng thái và vòng đời của Lit.
Tiểu bang
Khái niệm "Thuộc tính phản ứng" của Lit là sự kết hợp giữa trạng thái và đạo cụ của React. Thuộc tính phản ứng khi bị thay đổi có thể kích hoạt vòng đời thành phần. Thuộc tính phản ứng có 2 biến thể:
Thuộc tính phản ứng công khai
// 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';
}
- Do
@property
xác định - Tương tự như các thành phần và trạng thái của React nhưng có thể thay đổi
- API công khai do người sử dụng thành phần truy cập và thiết lập
Trạng thái phản ứng nội bộ
// 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';
}
- Do
@state
xác định - Tương tự như trạng thái của React nhưng có thể thay đổi
- Trạng thái nội bộ riêng tư thường được truy cập từ bên trong thành phần hoặc các lớp con
Vòng đời
Vòng đời Lit khá giống với vòng đời của React, nhưng có một số điểm khác biệt đáng chú ý.
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';
}
}
- Lượng lit tương đương cũng là
constructor
- Không cần phải chuyển bất cứ nội dung nào đến lệnh gọi cấp cao
- Người gọi (không hoàn toàn dành cho tất cả mọi người):
document.createElement
document.innerHTML
new ComponentClass()
- Nếu trang có tên thẻ chưa được nâng cấp và định nghĩa được tải và đăng ký bằng
@customElement
hoặccustomElements.define
- Có chức năng tương tự
constructor
của React
render
// React
render() {
return <div>Hello World</div>
}
// Lit
render() {
return html`<div>Hello World</div>`;
}
- Lượng lit tương đương cũng là
render
- Có thể trả về bất kỳ kết quả có thể kết xuất nào, ví dụ:
TemplateResult
hoặcstring
, v.v. - Tương tự như React,
render()
phải là một hàm thuần tuý - Sẽ hiển thị cho bất kỳ nút nào
createRenderRoot()
trả về (ShadowRoot
theo mặc định)
componentDidMount
componentDidMount
tương tự như tổ hợp cả phương thức gọi lại trong vòng đời firstUpdated
và connectedCallback
của Lit.
firstUpdated
import Chart from 'chart.js';
// React
componentDidMount() {
this._chart = new Chart(this.chartElRef.current, {...});
}
// Lit
firstUpdated() {
this._chart = new Chart(this.chartEl, {...});
}
- Được gọi lần đầu tiên mẫu của thành phần được kết xuất vào thư mục gốc của thành phần
- Sẽ chỉ được gọi nếu phần tử được kết nối, ví dụ: không được gọi qua
document.createElement('my-component')
cho đến khi nút đó được nối vào cây DOM - Đây là vị trí tốt để thực hiện thiết lập thành phần yêu cầu DOM được kết xuất bởi thành phần
- Không giống như các thay đổi
componentDidMount
của React đối với thuộc tính phản ứng trongfirstUpdated
sẽ dẫn đến kết xuất lại, mặc dù trình duyệt thường sẽ đưa các thay đổi vào cùng một khung. Nếu những thay đổi đó không yêu cầu quyền truy cập vào DOM của thư mục gốc, thì chúng thường nên được đưa vàowillUpdate
connectedCallback
// React
componentDidMount() {
this.window.addEventListener('resize', this.boundOnResize);
}
// Lit
connectedCallback() {
super.connectedCallback();
this.window.addEventListener('resize', this.boundOnResize);
}
- Được gọi bất cứ khi nào phần tử tuỳ chỉnh được chèn vào cây DOM
- Không giống như các thành phần React, khi các phần tử tuỳ chỉnh được tách khỏi DOM, chúng sẽ không bị huỷ bỏ và do đó có thể được "kết nối" nhiều lần
firstUpdated
sẽ không được gọi lại
- Hữu ích khi khởi động lại DOM hoặc đính kèm lại trình nghe sự kiện đã được dọn dẹp khi ngắt kết nối
- Lưu ý:
connectedCallback
có thể được gọi trướcfirstUpdated
. Vì vậy, trong lần gọi đầu tiên, DOM có thể không có sẵn
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);
}
}
- Văn bản tương đương là
updated
(sử dụng thì quá khứ trong tiếng Anh của "update") - Không giống như React,
updated
cũng được gọi trong lần kết xuất ban đầu - Có chức năng tương tự
componentDidUpdate
của React
componentWillUnmount
// React
componentWillUnmount() {
this.window.removeEventListener('resize', this.boundOnResize);
}
// Lit
disconnectedCallback() {
super.disconnectedCallback();
this.window.removeEventListener('resize', this.boundOnResize);
}
- Lít tương đương với
disconnectedCallback
- Không giống như các thành phần React, khi các phần tử tuỳ chỉnh được tách khỏi DOM, thành phần này sẽ không bị huỷ bỏ
- Không giống như
componentWillUnmount
,disconnectedCallback
được gọi sau khi phần tử bị xoá khỏi cây - DOM bên trong thư mục gốc vẫn được gắn với cây con của thư mục gốc
- Hữu ích trong việc dọn dẹp trình nghe sự kiện và các tệp tham chiếu bị rò rỉ để trình duyệt có thể thu thập thành phần vào thùng rác
Bài tập
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')
);
Trong ví dụ trên, có một đồng hồ đơn giản thực hiện những việc sau:
- Kết xuất này hiện thông báo "Hello World! Đó là" sau đó hiện thời gian
- Mỗi giây, đồng hồ sẽ cập nhật
- Khi được tháo, mã đánh dấu sẽ xoá khoảng thời gian gọi kim đánh dấu nhịp độ khung hình
Trước tiên, hãy bắt đầu với phần khai báo lớp thành phần:
// 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);
Tiếp theo, hãy khởi chạy date
và khai báo thuộc tính này là thuộc tính phản ứng nội bộ với @state
vì người dùng thành phần này sẽ không thiết lập date
trực tiếp.
// 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);
Tiếp theo, hãy hiển thị mẫu.
// Lit (JS & TS)
render() {
return html`
<div>
<h1>Hello, World!</h1>
<h2>It is ${this.date.toLocaleTimeString()}.</h2>
</div>
`;
}
Bây giờ, hãy triển khai phương thức đánh dấu nhịp độ khung hình.
tick() {
this.date = new Date();
}
Tiếp theo là việc triển khai componentDidMount
. Xin nhắc lại, Lit tương tự là sự kết hợp của firstUpdated
và connectedCallback
. Trong trường hợp của thành phần này, việc gọi tick
bằng setInterval
không yêu cầu quyền truy cập vào DOM bên trong thư mục gốc. Ngoài ra, khoảng thời gian sẽ bị xoá khi phần tử bị xoá khỏi cây tài liệu. Vì vậy, nếu được đính kèm lại, khoảng thời gian sẽ cần phải bắt đầu lại. Do đó, connectedCallback
là lựa chọn tốt hơn ở đây.
// 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
);
}
Cuối cùng, hãy dọn dẹp khoảng thời gian để khoảng thời gian này không thực thi dấu đánh dấu sau khi phần tử bị ngắt kết nối khỏi cây tài liệu.
// Lit (TS & JS)
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this.timerId);
}
Kết hợp tất cả lại với nhau, mã sẽ có dạng như sau:
// 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. Móc
Trong phần này, bạn sẽ tìm hiểu cách dịch các khái niệm của React Hook sang Lit.
Khái niệm về hook của React
Các hook phản ứng giúp các thành phần hàm "hook" trạng thái. Việc này mang lại một số lợi ích.
- Chúng đơn giản hoá việc sử dụng lại logic có trạng thái
- Giúp chia một thành phần thành các hàm nhỏ hơn
Ngoài ra, trọng tâm vào các thành phần dựa trên hàm giải quyết một số vấn đề nhất định với cú pháp dựa trên lớp của React, chẳng hạn như:
- Phải vượt
props
từconstructor
đếnsuper
- Khởi chạy thuộc tính không gọn gàng trong
constructor
- Đây là lý do mà nhóm React đưa ra vào thời điểm đó nhưng đã được giải quyết bằng ES2019
- Các vấn đề do
this
không còn tham chiếu đến thành phần này nữa
Các khái niệm về phản ứng của hook trong Lit
Như đã đề cập trong phần Thành phần & Trong phần Props, Lit không đưa ra cách tạo phần tử tuỳ chỉnh từ một hàm, nhưng LitElement giải quyết được hầu hết các vấn đề chính liên quan đến các thành phần của lớp React. Ví dụ:
// 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 giải quyết các vấn đề này như thế nào?
constructor
không nhận đối số- Tất cả các liên kết
@event
đều tự động liên kết vớithis
this
trong hầu hết các trường hợp đề cập đến tham chiếu của phần tử tùy chỉnh- Giờ đây, bạn có thể tạo thực thể cho các thuộc tính của lớp dưới dạng thành phần của lớp. Thao tác này dọn dẹp các phương thức triển khai dựa trên hàm khởi tạo
Bộ điều khiển phản ứng
Các khái niệm chính đằng sau Hooks tồn tại trong Lit dưới dạng bộ điều khiển phản ứng. Các mẫu bộ điều khiển phản ứng cho phép chia sẻ logic trạng thái, chia các thành phần thành các bit nhỏ hơn, nhiều mô-đun hơn, cũng như nối vào vòng đời cập nhật của một phần tử.
Bộ điều khiển phản ứng là giao diện đối tượng có thể kết nối với vòng đời cập nhật của một máy chủ điều khiển như LitElement.
Vòng đời của ReactiveController
và reactiveControllerHost
là:
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>;
}
Bằng cách xây dựng một trình điều khiển phản ứng và đính kèm trình điều khiển đó vào một máy chủ bằng addController
, vòng đời của trình điều khiển này sẽ được gọi cùng với vòng đời của máy chủ. Ví dụ: hãy xem lại ví dụ về đồng hồ trong phần Trạng thái & Mục Vòng đời:
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')
);
Trong ví dụ trên, có một đồng hồ đơn giản thực hiện những việc sau:
- Kết xuất này hiện thông báo "Hello World! Đó là" sau đó hiện thời gian
- Mỗi giây, đồng hồ sẽ cập nhật
- Khi được tháo, mã đánh dấu sẽ xoá khoảng thời gian gọi kim đánh dấu nhịp độ khung hình
Xây dựng giàn giáo thành phần
Trước tiên, hãy bắt đầu bằng việc khai báo lớp thành phần và thêm hàm 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);
Xây dựng bộ điều khiển
Bây giờ, hãy chuyển sang clock.ts
và tạo một lớp cho ClockController
rồi thiết lập 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() {
}
}
Bạn có thể xây dựng bộ điều khiển phản ứng theo bất kỳ cách nào miễn là có chung giao diện ReactiveController
, nhưng việc sử dụng một lớp có constructor
có thể lấy giao diện ReactiveControllerHost
cũng như mọi thuộc tính cần thiết khác để khởi tạo bộ điều khiển sẽ là mẫu mà nhóm Lit thích sử dụng cho hầu hết các trường hợp cơ bản.
Bây giờ, bạn cần dịch các phương thức gọi lại trong vòng đời của React sang các phương thức gọi lại của bộ điều khiển. Nói ngắn gọn:
componentDidMount
- Đến
connectedCallback
của LitElement - Đến
hostConnected
của bộ điều khiển
- Đến
ComponentWillUnmount
- Đến
disconnectedCallback
của LitElement - Đến
hostDisconnected
của bộ điều khiển
- Đến
Để biết thêm thông tin về cách dịch vòng đời của React sang vòng đời Lit, hãy xem phần Trạng thái và Vòng đời.
Tiếp theo, hãy triển khai lệnh gọi lại hostConnected
và các phương thức tick
, đồng thời dọn dẹp khoảng thời gian trong hostDisconnected
như đã thực hiện trong ví dụ ở phần Trạng thái & Vòng đời.
// 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);
}
}
Sử dụng bộ điều khiển
Để sử dụng trình điều khiển đồng hồ, hãy nhập bộ điều khiển và cập nhật thành phần trong index.ts
hoặc 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);
Để sử dụng tay điều khiển, bạn cần tạo thực thể cho tay điều khiển bằng cách truyền một tham chiếu đến máy chủ lưu trữ tay điều khiển (là thành phần <my-element>
), sau đó sử dụng nó trong phương thức render
.
Kích hoạt kết xuất lại trong bộ điều khiển
Lưu ý rằng ứng dụng sẽ hiển thị thời gian, nhưng thời gian thì không được cập nhật. Lý do là đơn vị điều khiển đang đặt ngày mỗi giây, nhưng máy chủ không cập nhật. Nguyên nhân là do date
đang thay đổi trên lớp ClockController
và không còn thay đổi trên thành phần này nữa. Tức là sau khi đặt date
trên tay điều khiển, máy chủ lưu trữ cần được yêu cầu chạy vòng đời cập nhật bằng host.requestUpdate()
.
// Lit (TS & JS) - clock.ts / clock.js
private tick() {
this.date = new Date();
this.host.requestUpdate();
}
Lúc này, đồng hồ chắc hẳn đang kêu tích tắc!
Để so sánh chi tiết hơn về các trường hợp sử dụng phổ biến với nội dung hấp dẫn, vui lòng xem phần Chủ đề nâng cao – Móc.
8. Thiếu nhi
Trong phần này, bạn sẽ tìm hiểu cách sử dụng khung giờ để quản lý phần tử con trong Lit.
Máy đánh bạc và Con
Các ô cho phép kết hợp bằng cách cho phép bạn lồng các thành phần.
Trong React, trẻ được kế thừa thông qua các đạo cụ. Ô mặc định là props.children
và hàm render
xác định vị trí của ô mặc định. Ví dụ:
const MyArticle = (props) => {
return <article>{props.children}</article>;
};
Lưu ý props.children
là Thành phần phản ứng chứ không phải là phần tử HTML.
Trong Lit, các thành phần con được bao gồm trong hàm kết xuất với các phần tử ô. Lưu ý rằng thành phần con không được kế thừa theo cách tương tự như React. Ở chế độ Lit, phần tử con là HTMLElements được đính kèm vào ô. Tệp đính kèm này được gọi là Phép chiếu (Projection).
@customElement("my-article")
export class MyArticle extends LitElement {
render() {
return html`
<article>
<slot></slot>
</article>
`;
}
}
Nhiều khung giờ
Trong React, việc thêm nhiều ô về cơ bản cũng giống như việc kế thừa nhiều tài sản hơn.
const MyArticle = (props) => {
return (
<article>
<header>
{props.headerChildren}
</header>
<section>
{props.sectionChildren}
</section>
</article>
);
};
Tương tự như vậy, việc thêm nhiều phần tử <slot>
sẽ tạo ra nhiều ô hơn trong Lit. Nhiều ô được xác định bằng thuộc tính name
: <slot name="slot-name">
. Việc này cho phép nhà xuất bản con khai báo khung giờ mà trẻ sẽ được chỉ định.
@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>
`;
}
}
Nội dung vị trí mặc định
Vùng quảng cáo sẽ hiển thị cây con khi không có nút nào được chiếu tới vùng đó. Khi các nút được chiếu lên một vị trí, vị trí đó sẽ không hiển thị cây con và thay vào đó sẽ hiển thị các nút được chiếu.
@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>
`;
}
}
Chỉ định trẻ vào các khung giờ
Trong React, thành phần con được gán cho các ô thông qua các thuộc tính của một Thành phần. Trong ví dụ bên dưới, các phần tử React được truyền đến các thuộc tính headerChildren
và sectionChildren
.
const MyNewsArticle = () => {
return (
<MyArticle
headerChildren={<h3>Extry, Extry! Read all about it!</h3>}
sectionChildren={<p>Children are props in React!</p>}
/>
);
};
Trong Lit, phần tử con được chỉ định cho các ô bằng thuộc tính 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>
`;
}
}
Nếu không có vùng mặc định (ví dụ: <slot>
) và không có vùng nào có thuộc tính name
(ví dụ: <slot name="foo">
) khớp với thuộc tính slot
của phần tử con của phần tử tuỳ chỉnh (ví dụ: <div slot="foo">
), thì nút đó sẽ không được chiếu và không xuất hiện.
9. Tham chiếu
Đôi khi, nhà phát triển có thể cần phải truy cập vào API của một HTMLElement.
Trong phần này, bạn sẽ tìm hiểu cách lấy thông tin tham chiếu phần tử trong Lit.
Tham chiếu phản ứng
Thành phần React được chuyển đổi thành một loạt lệnh gọi hàm để tạo DOM ảo khi được gọi. DOM ảo này được diễn giải bằng ReactDOM và hiển thị HTMLElements.
Trong React, Tham chiếu là không gian trong bộ nhớ để chứa một HTMLElement được tạo.
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>
);
};
Trong ví dụ trên, thành phần React sẽ thực hiện những việc sau:
- Kết xuất phần nhập văn bản trống và một nút có văn bản
- Lấy tiêu điểm nhập khi nhấp vào nút này
Sau lần kết xuất ban đầu, React sẽ đặt inputRef.current
thành HTMLInputElement
được tạo thông qua thuộc tính ref
.
Phát ngôn "Tài liệu tham khảo" với @query
Lit hoạt động gần trình duyệt và tạo ra một bản tóm tắt rất nhỏ so với các tính năng gốc của trình duyệt.
React tương đương với refs
trong Lit là HTMLElement do trình trang trí @query
và @queryAll
trả về.
@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>
`;
}
}
Trong ví dụ trên, thành phần Lit thực hiện những việc sau:
- Xác định một thuộc tính trên
MyElement
bằng cách sử dụng trình trang trí@query
(tạo phương thức getter choHTMLInputElement
). - Khai báo và đính kèm một lệnh gọi lại sự kiện nhấp chuột có tên là
onButtonClick
. - Tập trung nội dung nhập vào khi nhấp vào nút
Trong JavaScript, trình trang trí @query
và @queryAll
lần lượt thực hiện querySelector
và querySelectorAll
. Đây là JavaScript tương đương với @query('input') inputEl!: HTMLInputElement;
get inputEl() {
return this.renderRoot.querySelector('input');
}
Sau khi thành phần Lit xác nhận mẫu của phương thức render
vào gốc của my-element
, trình trang trí @query
sẽ cho phép inputEl
trả về phần tử input
đầu tiên tìm thấy trong gốc kết xuất. Hàm này sẽ trả về null
nếu @query
không tìm thấy phần tử đã chỉ định.
Nếu có nhiều phần tử input
trong gốc kết xuất, @queryAll
sẽ trả về một danh sách các nút.
10. Trạng thái dàn xếp
Trong phần này, bạn sẽ tìm hiểu cách dàn xếp trạng thái giữa các thành phần trong Lit.
Các thành phần có thể tái sử dụng
Phản ứng bắt chước quy trình kết xuất chức năng với luồng dữ liệu từ trên xuống. Cha mẹ đưa ra trạng thái cho trẻ thông qua các đạo cụ. Trẻ giao tiếp với cha mẹ thông qua các lệnh gọi lại có trong đạo cụ.
const CounterButton = (props) => {
const label = props.step < 0
? `- ${-1 * props.step}`
: `+ ${props.step}`;
return (
<button
onClick={() =>
props.addToCounter(props.step)}>{label}</button>
);
};
Trong ví dụ trên, thành phần React sẽ thực hiện những việc sau:
- Tạo nhãn dựa trên giá trị
props.step
. - Hiển thị nút có nhãn +step hoặc -step
- Cập nhật thành phần mẹ bằng cách gọi
props.addToCounter
vớiprops.step
làm đối số khi nhấp chuột
Mặc dù có thể truyền lệnh gọi lại trong Lit, nhưng các mẫu thông thường sẽ khác. Thành phần React trong ví dụ trên có thể được viết dưới dạng Thành phần Lit trong ví dụ bên dưới:
@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>
`;
}
}
Trong ví dụ trên, một Thành phần Lit sẽ hoạt động như sau:
- Tạo thuộc tính phản ứng
step
- Điều phối một sự kiện tuỳ chỉnh có tên là
update-counter
mang giá trịstep
của phần tử khi nhấp vào
Các sự kiện trên trình duyệt xuất hiện từ phần tử con đến các phần tử mẹ. Sự kiện cho phép trẻ truyền phát các sự kiện tương tác và thay đổi trạng thái. Về cơ bản, phản ứng sẽ truyền trạng thái theo hướng ngược lại. Vì vậy, sẽ ít phổ biến khi thấy các Thành phần phản ứng được gửi đi và theo dõi các sự kiện theo cách tương tự như Thành phần Lit.
Thành phần có trạng thái
Trong React, bạn nên sử dụng hook để quản lý trạng thái. Bạn có thể tạo một Thành phần MyCounter
bằng cách sử dụng lại Thành phần CounterButton
. Hãy lưu ý cách truyền addToCounter
đến cả hai thực thể của CounterButton
.
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>
);
};
Ví dụ trên thực hiện những việc sau:
- Tạo trạng thái
count
. - Tạo lệnh gọi lại để thêm một số vào trạng thái
count
. CounterButton
sử dụngaddToCounter
để cập nhậtcount
thêmstep
cho mỗi lượt nhấp.
Bạn có thể triển khai MyCounter
tương tự trong Lit. Hãy lưu ý cách addToCounter
không được truyền đến counter-button
. Thay vào đó, lệnh gọi lại được liên kết dưới dạng trình nghe sự kiện với sự kiện @update-counter
trên một phần tử mẹ.
@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>
`;
}
}
Ví dụ trên thực hiện những việc sau:
- Tạo một thuộc tính phản ứng có tên là
count
. Thuộc tính này sẽ cập nhật thành phần khi giá trị thay đổi - Liên kết lệnh gọi lại
addToCounter
với trình nghe sự kiện@update-counter
- Cập nhật
count
bằng cách thêm giá trị có trongdetail.step
của sự kiệnupdate-counter
- Đặt giá trị
step
củacounter-button
thông qua thuộc tínhstep
Cách thông thường hơn là sử dụng thuộc tính phản ứng trong Lit để thông báo về những thay đổi của cha mẹ sang con. Tương tự, bạn nên sử dụng hệ thống sự kiện của trình duyệt để tạo bong bóng thông tin chi tiết từ dưới lên.
Phương pháp này tuân theo các phương pháp hay nhất và tuân thủ mục tiêu của Lit là hỗ trợ các thành phần web trên nhiều nền tảng.
11. Định kiểu
Trong phần này, bạn sẽ tìm hiểu về cách tạo kiểu trong Lit.
Định kiểu
Tính năng Ánh sáng cung cấp nhiều cách tạo kiểu cho phần tử cũng như một giải pháp tích hợp sẵn.
Kiểu cùng dòng
Tính năng Lit hỗ trợ các kiểu cùng dòng cũng như liên kết với các kiểu đó.
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>
`;
}
}
Trong ví dụ trên, có 2 tiêu đề, mỗi tiêu đề có một kiểu cùng dòng.
Giờ thì hãy nhập và liên kết đường viền từ border-color.js
với văn bản màu cam:
...
import borderColor from './border-color.js';
...
html`
...
<h1 style="color:orange;${borderColor}">This text is orange</h1>
...`
Việc phải tính toán chuỗi kiểu mỗi lần có thể sẽ gây khó chịu một chút, vì vậy Lit đưa ra một lệnh để giúp bạn thực hiện việc này.
styleMap
Lệnh styleMap
giúp bạn dễ dàng sử dụng JavaScript để đặt kiểu cùng dòng. Ví dụ:
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>
`;
}
}
Ví dụ trên thực hiện những việc sau:
- Hiển thị
h1
có đường viền và công cụ chọn màu - Thay đổi
border-color
thành giá trị từ công cụ chọn màu
Ngoài ra, còn có styleMap
dùng để thiết lập kiểu của h1
. styleMap
tuân theo cú pháp tương tự như cú pháp liên kết thuộc tính style
của React.
CSSResult
Bạn nên định kiểu cho các thành phần bằng cách sử dụng giá trị cố định trong mẫu được gắn thẻ 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>
`;
}
}
Ví dụ trên thực hiện những việc sau:
- Khai báo giá trị cố định của mẫu được gắn thẻ CSS kèm theo một liên kết
- Đặt màu cho hai
h1
bằng mã nhận dạng
Sau đây là các lợi ích khi sử dụng thẻ mẫu css
:
- Được phân tích cú pháp một lần cho mỗi lớp so với mỗi thực thể
- Được triển khai chú trọng đến khả năng tái sử dụng của mô-đun
- Có thể dễ dàng phân tách các kiểu thành các tệp riêng
- Tương thích với polyfill Thuộc tính tuỳ chỉnh của CSS
Ngoài ra, hãy chú ý đến thẻ <style>
trong index.html
:
<!-- index.html -->
<style>
h1 {
color: red !important;
}
</style>
Văn bản sẽ xác định phạm vi của các thành phần vào gốc của chúng. Tức là kiểu sẽ không bị rò rỉ cả ra lẫn vào bên trong. Để truyền kiểu từ các thành phần đến các thành phần, nhóm Lit khuyên bạn nên sử dụng Thuộc tính tuỳ chỉnh của CSS vì chúng có thể thâm nhập vào phạm vi của kiểu Lit.
Thẻ kiểu
Bạn cũng có thể chỉ cần đưa thẻ <style>
vào cùng dòng trong mẫu của mình. Trình duyệt sẽ loại bỏ các thẻ kiểu trùng lặp này, nhưng bằng cách đặt các thẻ đó vào mẫu của bạn, các thẻ đó sẽ được phân tích cú pháp cho từng thực thể thành phần thay vì mỗi lớp, như trong trường hợp mẫu được gắn thẻ css
. Ngoài ra, việc loại bỏ trùng lặp CSSResult
trên trình duyệt nhanh hơn nhiều.
Thẻ liên kết
Việc sử dụng <link rel="stylesheet">
trong mẫu của bạn cũng là một khả năng cho các kiểu, nhưng điều này cũng không được khuyến khích vì nó có thể gây ra ánh sáng flash ban đầu của nội dung không được định kiểu (FOUC).
12. Chủ đề nâng cao (không bắt buộc)
JSX và Tạo mẫu
Ánh sáng & DOM ảo
Lit-html không bao gồm DOM ảo thông thường có sự khác biệt ở từng nút riêng lẻ. Thay vào đó, công cụ này sử dụng các tính năng hiệu suất hàm nội tại với thông số kỹ thuật giá trị cố định mẫu được gắn thẻ của ES2015. Hằng mẫu được gắn thẻ là các chuỗi cố định của mẫu có đính kèm các hàm thẻ.
Dưới đây là một ví dụ về giá trị cố định của mẫu:
const str = 'string';
console.log(`This is a template literal ${str}`);
Dưới đây là ví dụ về giá trị cố định của mẫu được gắn thẻ:
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
Trong ví dụ trên, thẻ là hàm tag
và hàm f
trả về lệnh gọi một giá trị cố định mẫu được gắn thẻ.
Rất nhiều điều kỳ diệu về hiệu suất trong Lit bắt nguồn từ việc các mảng chuỗi được truyền vào hàm thẻ có cùng một con trỏ (như thể hiện trong console.log
thứ hai). Trình duyệt không tạo lại một mảng strings
mới trên mỗi lệnh gọi hàm thẻ vì trình duyệt này đang sử dụng cùng một giá trị cố định của mẫu (tức là ở cùng một vị trí trong AST). Vì vậy, các chức năng liên kết, phân tích cú pháp và lưu mẫu vào bộ nhớ đệm của Lit có thể tận dụng các tính năng này mà không gây ra nhiều mức hao tổn khác biệt trong thời gian chạy.
Hành vi tích hợp sẵn của trình duyệt của các giá trị cố định của mẫu được gắn thẻ mang lại cho Lit khá nhiều lợi thế về hiệu suất. Hầu hết các DOM ảo thông thường thực hiện phần lớn công việc của mình trong JavaScript. Tuy nhiên, giá trị cố định của mẫu được gắn thẻ thực hiện hầu hết những khác biệt trong C++ của trình duyệt.
Nếu bạn muốn bắt đầu sử dụng các giá trị cố định trong mẫu được gắn thẻ HTML bằng React hoặc Preact, thì nhóm Lit nên dùng thư viện htm
.
Mặc dù, như trong trường hợp trang web của Lớp học lập trình của Google và một số trình soạn thảo mã trực tuyến, bạn sẽ nhận thấy rằng việc làm nổi bật cú pháp bằng giá trị của mẫu được gắn thẻ không phổ biến lắm. Một số IDE và trình chỉnh sửa văn bản hỗ trợ các định dạng này theo mặc định, chẳng hạn như Atom và trình đánh dấu khối mã của GitHub. Nhóm Lit cũng phối hợp rất chặt chẽ với cộng đồng để duy trì các dự án như lit-plugin
– một trình bổ trợ VS Code giúp làm nổi bật cú pháp, kiểm tra loại và thông minh cho các dự án Lit của bạn.
Ánh sáng & JSX + DOM React
JSX không chạy trong trình duyệt mà thay vào đó sử dụng bộ tiền xử lý để chuyển đổi JSX thành các lệnh gọi hàm JavaScript (thường là qua Squarespace).
Ví dụ: iMessage sẽ biến đổi:
const element = <div className="title">Hello World!</div>;
ReactDOM.render(element, mountNode);
vào:
const element = React.createElement('div', {className: 'title'}, 'Hello World!');
ReactDOM.render(element, mountNode);
Sau đó, React DOM lấy đầu ra React và dịch sang DOM thực tế – thuộc tính, thuộc tính, trình nghe sự kiện và tất cả.
Lit-html sử dụng giá trị cố định của mẫu được gắn thẻ có thể chạy trong trình duyệt mà không cần dịch hoặc trình xử lý trước. Tức là để bắt đầu sử dụng Lit, bạn chỉ cần có tệp HTML, tập lệnh mô-đun ES và một máy chủ. Dưới đây là một tập lệnh hoàn toàn có thể chạy bằng trình duyệt:
<!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>
Ngoài ra, vì hệ thống tạo mẫu của Lit, lit-html, không sử dụng DOM ảo thông thường mà sử dụng trực tiếp DOM API, kích thước của Lit 2 dưới 5kb được giảm thiểu và được nén so với React (2,8kb) + 40kb rút gọn và giải nén của React-dom.
Sự kiện
React sử dụng một hệ thống sự kiện tổng hợp. Điều này có nghĩa là React-dom phải xác định mọi sự kiện sẽ được dùng trên mọi thành phần và cung cấp một trình nghe sự kiện camelCase tương đương cho từng loại nút. Do đó, JSX không có phương thức để xác định trình nghe sự kiện cho sự kiện tuỳ chỉnh và các nhà phát triển phải sử dụng ref
, sau đó bắt buộc phải áp dụng một trình nghe. Điều này tạo ra trải nghiệm tương đương cho nhà phát triển khi tích hợp các thư viện không lưu ý đến React, dẫn đến việc phải viết một trình bao bọc dành riêng cho React.
Lit-html truy cập trực tiếp vào DOM và sử dụng các sự kiện gốc, vì vậy, bạn có thể thêm trình nghe sự kiện dễ dàng như @event-name=${eventNameListener}
. Điều này có nghĩa là quá trình phân tích cú pháp trong thời gian chạy sẽ diễn ra ít hơn để thêm trình nghe sự kiện cũng như kích hoạt sự kiện.
Thành phần và Đồ hoá trang
Thành phần phản ứng & phần tử tuỳ chỉnh
Trong trường hợp này, LitElement sử dụng các phần tử tuỳ chỉnh để đóng gói các thành phần. Các phần tử tuỳ chỉnh gây ra một số điểm đánh đổi giữa các thành phần của React khi nói đến việc sắp xếp thành phần (trạng thái và vòng đời được thảo luận thêm trong phần Trạng thái và vòng đời).
Phần tử tuỳ chỉnh có một số ưu điểm như một hệ thống thành phần:
- Dành cho trình duyệt và không yêu cầu bất kỳ công cụ nào
- Phù hợp với mọi API trình duyệt từ
innerHTML
vàdocument.createElement
đếnquerySelector
- Thường có thể được dùng trên các khung
- Có thể đăng ký từng phần với
customElements.define
và tính năng "hydrate" Mô hình đối tượng tài liệu (DOM)
Một số nhược điểm của Phần tử tuỳ chỉnh so với các thành phần React:
- Bạn không thể tạo phần tử tuỳ chỉnh mà không xác định lớp (do đó sẽ không có các thành phần chức năng giống như JSX)
- Phải chứa thẻ đóng
- Lưu ý: mặc dù các nhà cung cấp trình duyệt tiện lợi cho nhà phát triển có xu hướng hối tiếc về thông số thẻ tự đóng, đó là lý do tại sao thông số kỹ thuật mới hơn có xu hướng không bao gồm thẻ tự đóng
- Đưa một nút bổ sung vào cây DOM, nút này có thể gây ra sự cố về bố cục
- Phải được đăng ký qua JavaScript
Lit kết hợp các thành phần tuỳ chỉnh thay vì một hệ thống thành phần riêng vì các thành phần tuỳ chỉnh được tích hợp vào trình duyệt. Nhóm Lit tin rằng lợi ích trên nhiều khung lớn hơn lợi ích mà lớp trừu tượng thành phần mang lại. Trên thực tế, những nỗ lực của nhóm Lit đã khắc phục được những vấn đề chính khi đăng ký JavaScript. Ngoài ra, một số công ty như GitHub tận dụng tính năng đăng ký tải từng phần tử tuỳ chỉnh để từng bước cải thiện các trang bằng sự tinh tế không bắt buộc.
Chuyển dữ liệu sang các phần tử tuỳ chỉnh
Một quan niệm sai lầm phổ biến về phần tử tuỳ chỉnh là bạn chỉ có thể truyền dữ liệu dưới dạng chuỗi. Quan niệm sai lầm này có thể xuất phát từ thực tế là các thuộc tính phần tử chỉ có thể được viết dưới dạng chuỗi. Mặc dù đúng là Lit sẽ truyền thuộc tính chuỗi cho các loại đã xác định, nhưng các phần tử tuỳ chỉnh cũng có thể chấp nhận dữ liệu phức tạp làm thuộc tính.
Ví dụ: theo định nghĩa LitElement sau đây:
// 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>`;
}
}
Thuộc tính phản ứng gốc num
được xác định. Thuộc tính này sẽ chuyển đổi giá trị chuỗi của một thuộc tính thành number
, sau đó cấu trúc dữ liệu phức tạp được đưa vào cùng với attribute:false
để tắt hoạt động xử lý thuộc tính của Lit.
Sau đây là cách chuyển dữ liệu vào phần tử tuỳ chỉnh này:
<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>
Tiểu bang và Vòng đời
Các phương thức gọi lại khác trong vòng đời phản ứng
static getDerivedStateFromProps
Không có quy tắc tương đương nào trong Lit vì đạo cụ và trạng thái đều giống nhau về thuộc tính lớp
shouldComponentUpdate
- Lít tương đương là
shouldUpdate
- Được gọi trong lần kết xuất đầu tiên, không giống như React
- Có chức năng tương tự
shouldComponentUpdate
của React
getSnapshotBeforeUpdate
Theo Lit, getSnapshotBeforeUpdate
tương tự như cả update
và willUpdate
willUpdate
- Đã gọi trước
update
- Không giống như
getSnapshotBeforeUpdate
,willUpdate
được gọi trướcrender
- Các thay đổi đối với thuộc tính phản ứng trong
willUpdate
không kích hoạt lại chu kỳ cập nhật - Vị trí phù hợp để tính toán các giá trị thuộc tính phụ thuộc vào các thuộc tính khác và được sử dụng trong phần còn lại của quá trình cập nhật
- Phương thức này được gọi trên máy chủ trong SSR, vì vậy bạn không nên truy cập vào DOM ở đây
update
- Được gọi sau
willUpdate
- Không giống như
getSnapshotBeforeUpdate
,update
được gọi trướcrender
- Các thay đổi đối với thuộc tính phản ứng trong
update
sẽ không kích hoạt lại chu kỳ cập nhật nếu bạn thay đổi trước khi gọisuper.update
- Vị trí phù hợp để thu thập thông tin từ DOM xung quanh thành phần trước khi kết xuất kết xuất được cam kết cho DOM
- Phương thức này không được gọi trên máy chủ trong SSR
Các phương thức gọi lại khác trong vòng đời Lit
Có một số phương thức gọi lại trong vòng đời không được đề cập trong phần trước vì không có phương thức tương tự nào trong React. Các yếu tố này là:
attributeChangedCallback
Phương thức này được gọi khi một trong các observedAttributes
của phần tử thay đổi. Cả observedAttributes
và attributeChangedCallback
đều nằm trong thông số kỹ thuật của các thành phần tuỳ chỉnh và được Lit triển khai nâng cao để cung cấp API thuộc tính cho các thành phần Lit.
adoptedCallback
Được gọi khi thành phần được di chuyển sang tài liệu mới, ví dụ: từ documentFragment
của HTMLTemplateElement
đến document
chính. Lệnh gọi lại này cũng nằm trong thông số kỹ thuật của phần tử tuỳ chỉnh và chỉ nên dùng cho các trường hợp sử dụng nâng cao khi thành phần thay đổi tài liệu.
Các phương thức và thuộc tính khác trong vòng đời
Các phương thức và thuộc tính này là các thành phần của lớp mà bạn có thể gọi, ghi đè hoặc chờ để giúp thao tác với quy trình vòng đời.
updateComplete
Đây là Promise
phân giải khi phần tử cập nhật xong vì vòng đời cập nhật và kết xuất không đồng bộ. Ví dụ:
async nextButtonClicked() {
this.step++;
// Wait for the next "step" state to render
await this.updateComplete;
this.dispatchEvent(new Event('step-rendered'));
}
getUpdateComplete
Đây là một phương thức sẽ được ghi đè để tuỳ chỉnh khi updateComplete
phân giải. Điều này thường xảy ra khi một thành phần đang kết xuất một thành phần con và chu kỳ kết xuất của các thành phần đó phải đồng bộ hoá. ví dụ:
class MyElement extends LitElement {
...
async getUpdateComplete() {
await super.getUpdateComplete();
await this.myChild.updateComplete;
}
}
performUpdate
Phương thức này gọi phương thức gọi lại trong vòng đời cập nhật. Việc cập nhật này thường không cần thiết, ngoại trừ một số ít trường hợp khi quá trình cập nhật phải được thực hiện đồng bộ hoặc để lên lịch tuỳ chỉnh.
hasUpdated
Thuộc tính này là true
nếu thành phần đã cập nhật ít nhất một lần.
isConnected
Là một phần của thông số kỹ thuật của phần tử tuỳ chỉnh, thuộc tính này sẽ là true
nếu phần tử đang được đính kèm vào cây tài liệu chính.
Trực quan hoá vòng đời cập nhật Lit
Có 3 phần trong vòng đời cập nhật:
- Trước khi cập nhật
- Cập nhật
- Sau khi cập nhật
Cập nhật trước
Sau ngày requestUpdate
, một bản cập nhật theo lịch sẽ chờ xử lý.
Cập nhật
Sau khi cập nhật
Móc
Lý do nên dùng nội dung hấp dẫn
Hook được đưa vào React cho các trường hợp sử dụng thành phần hàm đơn giản cần có trạng thái. Trong nhiều trường hợp đơn giản, các thành phần hàm có hook có xu hướng đơn giản và dễ đọc hơn nhiều so với các thành phần tương ứng trong lớp thành phần. Tuy nhiên, khi giới thiệu bản cập nhật trạng thái không đồng bộ cũng như truyền dữ liệu giữa các hook hoặc hiệu ứng, mẫu hook có xu hướng chưa đủ và giải pháp dựa trên lớp như bộ điều khiển phản ứng có xu hướng sẽ nổi bật.
Hook và yêu cầu API tay điều khiển
Thông thường, bạn có thể viết một hook yêu cầu dữ liệu từ một API. Ví dụ: lấy thành phần hàm React này thực hiện như sau:
index.tsx
- Hiển thị văn bản
- Hiển thị phản hồi của
useAPI
- Mã nhận dạng người dùng + Tên người dùng
- Thông báo Lỗi
- 404 khi tiếp cận người dùng 11 (theo thiết kế)
- Lỗi huỷ bỏ nếu tìm nạp API bị huỷ
- Đang tải thông báo
- Hiển thị nút hành động
- Người dùng tiếp theo: tìm nạp API cho người dùng tiếp theo
- Huỷ: thao tác này sẽ huỷ việc tìm nạp API và hiển thị lỗi
useApi.tsx
- Xác định một hook tuỳ chỉnh
useApi
- Sẽ không đồng bộ tìm nạp đối tượng người dùng từ một API
- Phát ra:
- Tên người dùng
- Liệu hoạt động tìm nạp có đang tải hay không
- Mọi thông báo lỗi
- Lệnh gọi lại để huỷ tìm nạp
- Huỷ tìm nạp đang diễn ra nếu bị tháo khỏi
- Xác định một hook tuỳ chỉnh
Sau đây là cách triển khai Lit + Bộ điều khiển phản ứng.
Những điểm cần nhớ:
- Bộ điều khiển phản ứng gần giống với hook tuỳ chỉnh
- Truyền dữ liệu không kết xuất được giữa các lệnh gọi lại và hiệu ứng
- React sử dụng
useRef
để truyền dữ liệu giữauseEffect
vàuseCallback
- Văn bản nhỏ sử dụng một thuộc tính lớp riêng tư
- Phản ứng về cơ bản là bắt chước hành vi của một thuộc tính lớp riêng tư
- React sử dụng
Ngoài ra, nếu bạn thực sự thích cú pháp thành phần hàm React có các hook nhưng sử dụng cùng một môi trường không có bản dựng của Lit, nhóm Lit khuyên bạn nên sử dụng thư viện Haunted.
Thiếu nhi
Vị trí mặc định
Khi bạn không cung cấp thuộc tính slot
cho các phần tử HTML, chúng sẽ được gán vào một vùng mặc định chưa đặt tên. Trong ví dụ bên dưới, MyApp
sẽ đặt một đoạn vào một ô được đặt tên. Theo mặc định, đoạn văn bản còn lại sẽ là đoạn văn bản chưa đặt tên".
@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>
`;
}
}
Cập nhật vị trí
Khi cấu trúc của các thành phần con vị trí thay đổi, sự kiện slotchange
sẽ được kích hoạt. Thành phần Lit có thể liên kết một trình nghe sự kiện với sự kiện slotchange
. Trong ví dụ bên dưới, vị trí đầu tiên tìm thấy trong shadowRoot
sẽ có assignedNodes tương ứng được ghi vào bảng điều khiển trên 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>
`;
}
}
Tham chiếu
Tạo tệp đối chiếu
Cả Lit và React đều hiển thị tham chiếu đến một HTMLElement sau khi các hàm render
của chúng được gọi. Tuy nhiên, bạn nên xem lại cách React và Lit sáng tạo DOM, sau đó được trả về thông qua trình trang trí Lit @query
hoặc tham chiếu React.
React là một quy trình chức năng giúp tạo các Thành phần React chứ không phải HTMLElements. Vì một Tham chiếu được khai báo trước khi kết xuất HTMLElement, nên một không gian trong bộ nhớ sẽ được phân bổ. Đây là lý do tại sao bạn thấy null
là giá trị ban đầu của Ref, vì phần tử DOM thực tế chưa được tạo (hoặc hiển thị), tức là useRef(null)
.
Sau khi ReactDOM chuyển đổi Thành phần React thành HTMLElement, hệ thống sẽ tìm một thuộc tính có tên là ref
trong ReactComponent. Nếu có, ReactDOM sẽ đặt tham chiếu của HTMLElement thành ref.current
.
LitElement sử dụng hàm thẻ mẫu html
từ lit-html để soạn Phần tử mẫu nâng cao. LitElement gắn nhãn nội dung của mẫu vào DOM tối của phần tử tuỳ chỉnh sau khi hiển thị. DOM bóng là một cây DOM có phạm vi được bao bọc bởi một gốc bóng đổ. Sau đó, trình trang trí @query
sẽ tạo một phương thức getter cho thuộc tính mà về cơ bản, thực hiện this.shadowRoot.querySelector
trên thư mục gốc có phạm vi.
Truy vấn nhiều phần tử
Trong ví dụ dưới đây, trình trang trí @queryAll
sẽ trả về hai đoạn văn bản ở gốc bóng đổ dưới dạng một 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>
`;
}
}
Về cơ bản, @queryAll
tạo một phương thức getter cho paragraphs
để trả về kết quả this.shadowRoot.querySelectorAll()
. Trong JavaScript, một phương thức getter có thể được khai báo để thực hiện cùng một mục đích:
get paragraphs() {
return this.renderRoot.querySelectorAll('p');
}
Các phần tử thay đổi truy vấn
Trình trang trí @queryAsync
phù hợp hơn để xử lý một nút có thể thay đổi dựa trên trạng thái của một thuộc tính phần tử khác.
Trong ví dụ bên dưới, @queryAsync
sẽ tìm phần tử đoạn đầu tiên. Tuy nhiên, phần tử đoạn sẽ chỉ hiển thị khi renderParagraph
tạo ngẫu nhiên một số lẻ. Lệnh @queryAsync
sẽ trả về một lời hứa sẽ phân giải khi có đoạn đầu tiên.
@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()}
`;
}
}
Trạng thái dàn xếp
Trong React, theo quy ước là sử dụng lệnh gọi lại vì trạng thái được trung gian bởi chính React. Phản ứng tốt nhất là không dựa vào trạng thái do các phần tử cung cấp. DOM chỉ đơn giản là một hiệu ứng của quá trình kết xuất.
Trạng thái bên ngoài
Bạn có thể sử dụng Redux, AdMob hoặc bất kỳ thư viện quản lý trạng thái nào khác cùng với Lit.
Các thành phần văn bản được tạo trong phạm vi trình duyệt. Vì vậy, bất kỳ thư viện nào cũng tồn tại trong phạm vi trình duyệt đều có sẵn cho Lit. Nhiều thư viện thú vị đã được xây dựng để sử dụng các hệ thống quản lý trạng thái hiện có trong Lit.
Sau đây là loạt video của Vaadin giải thích cách tận dụng Redux trong thành phần Lit.
Hãy xem lit-mobx của Adobe để xem một trang web có quy mô lớn có thể tận dụng AdMob trong Lit như thế nào.
Ngoài ra, hãy tham khảo Apollo Elements để biết cách các nhà phát triển đưa GraphQL vào các thành phần web của họ.
Lit hoạt động với các tính năng gốc của trình duyệt và có thể sử dụng hầu hết các giải pháp quản lý trạng thái trong phạm vi trình duyệt trong thành phần Lit.
Định kiểu
DOM bóng
Để đóng gói các kiểu và DOM trong một Phần tử tuỳ chỉnh một cách tự nhiên, Lit sử dụng DOM tối. Rễ bóng tạo ra một cây bóng đổ tách biệt với cây tài liệu chính. Tức là hầu hết các kiểu đều thuộc phạm vi của tài liệu này. Một số kiểu nhất định bị rò rỉ như màu sắc và các kiểu khác liên quan đến phông chữ.
Shadow DOM cũng giới thiệu các khái niệm và bộ chọn mới cho thông số 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.
*/
}
Kiểu chia sẻ
Lit giúp bạn dễ dàng chia sẻ kiểu giữa các thành phần dưới dạng CSSTemplateResults
thông qua thẻ mẫu css
. Ví dụ:
// 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>`
}
}
Giao diện
Gốc đổ bóng cho thấy một chút thách thức đối với việc thiết kế giao diện thông thường, thường là phương pháp gắn thẻ kiểu từ trên xuống. Cách thông thường để xử lý giao diện bằng các Thành phần web sử dụng Shadow DOM là hiển thị một API kiểu thông qua Thuộc tính tuỳ chỉnh CSS. Ví dụ: đây là mẫu mà Material Design sử dụng:
.mdc-textfield-outline {
border-color: var(--mdc-theme-primary, /* default value */ #...);
}
.mdc-textfield--input {
caret-color: var(--mdc-theme-primary, #...);
}
Sau đó, người dùng thay đổi giao diện của trang web bằng cách áp dụng các giá trị thuộc tính tuỳ chỉnh:
html {
--mdc-theme-primary: #F00;
}
html[dark] {
--mdc-theme-primary: #F88;
}
Nếu bạn bắt buộc phải sắp xếp giao diện từ trên xuống và không thể hiển thị kiểu, thì bạn luôn có thể tắt DOM bóng bằng cách ghi đè createRenderRoot
để trả về this
. Sau đó, bạn sẽ hiển thị các thành phần của mình vào chính phần tử tuỳ chỉnh thay vì vào gốc bóng được đính kèm với phần tử tuỳ chỉnh. Với thao tác này, bạn sẽ mất: đóng gói kiểu, đóng gói DOM và vị trí.
Sản xuất
IE 11
Nếu bạn cần hỗ trợ các trình duyệt cũ hơn như IE 11, bạn sẽ phải tải một số polyfill có kích thước khoảng 33kb khác. Bạn có thể tìm thêm thông tin tại đây.
Gói có điều kiện
Nhóm Lit đề xuất phân phối hai gói khác nhau, một gói cho IE 11 và một gói cho các trình duyệt hiện đại. Việc này mang lại một số lợi ích như sau:
- Phục vụ ES 6 nhanh hơn và sẽ phục vụ hầu hết khách hàng của bạn
- ES 5 được dịch mã làm tăng đáng kể kích thước gói
- Gói có điều kiện mang đến cho bạn những lợi ích tốt nhất ở cả hai mặt
- Hỗ trợ IE 11
- Không làm chậm các trình duyệt hiện đại
Bạn có thể xem thêm thông tin về cách tạo gói được phân phát có điều kiện trên trang web tài liệu của chúng tôi tại đây.