Xây dựng các thành phần web có thể tương tác thậm chí hoạt động với React PlatoBlockchain Data Intelligence. Tìm kiếm dọc. Ái.

Xây dựng các thành phần web có thể tương tác thậm chí hoạt động với React

Những người trong chúng ta, những người đã từng là nhà phát triển web hơn một vài năm có lẽ đã viết mã sử dụng nhiều hơn một khung JavaScript. Với tất cả các lựa chọn ngoài kia - React, Svelte, Vue, Angular, Solid - tất cả đều không thể tránh khỏi. Một trong những điều khó chịu hơn mà chúng tôi phải đối phó khi làm việc trên các khung công tác là tạo lại tất cả các thành phần giao diện người dùng cấp thấp đó: nút, tab, trình đơn thả xuống, v.v. Điều đặc biệt khó chịu là chúng tôi thường xác định chúng trong một khung công tác. , nói React, nhưng sau đó cần phải viết lại chúng nếu chúng ta muốn xây dựng thứ gì đó trong Svelte. Hoặc Vue. Hoặc rắn. Và như thế.

Sẽ không tốt hơn nếu chúng ta có thể xác định các thành phần giao diện người dùng cấp thấp này một lần, theo một cách bất khả tri của khuôn khổ và sau đó sử dụng lại chúng giữa các khuôn khổ? Tất nhiên nó sẽ! Và chúng tôi có thể; các thành phần web là cách. Bài đăng này sẽ cho bạn thấy làm thế nào.

Hiện tại, câu chuyện SSR cho các thành phần web còn thiếu một chút. Decl Compare shadow DOM (DSD) là cách một thành phần web được hiển thị từ phía máy chủ, nhưng theo bài viết này, nó không được tích hợp với các khung ứng dụng yêu thích của bạn như Next, Remix hoặc SvelteKit. Nếu đó là yêu cầu đối với bạn, hãy đảm bảo kiểm tra trạng thái mới nhất của DSD. Nhưng nếu không, nếu SSR không phải là thứ bạn đang sử dụng, hãy đọc tiếp.

Đầu tiên, một số ngữ cảnh

Thành phần Web về cơ bản là các phần tử HTML mà bạn tự định nghĩa, như <yummy-pizza> hoặc bất cứ điều gì, từ cơ bản. Chúng được đề cập ở đây tại CSS-Tricks (bao gồm một loạt phim mở rộng của Caleb Williamsmột của John Rhea) nhưng chúng tôi sẽ hướng dẫn ngắn gọn quá trình này. Về cơ bản, bạn xác định một lớp JavaScript, kế thừa nó từ HTMLElement, và sau đó xác định bất kỳ thuộc tính, thuộc tính và kiểu nào mà thành phần web có và tất nhiên, đánh dấu mà thành phần web cuối cùng sẽ hiển thị cho người dùng của bạn.

Có thể xác định các phần tử HTML tùy chỉnh không bị ràng buộc với bất kỳ thành phần cụ thể nào là điều thú vị. Nhưng sự tự do này cũng là một hạn chế. Tồn tại độc lập với bất kỳ khung JavaScript nào có nghĩa là bạn không thể thực sự tương tác với các khung JavaScript đó. Hãy nghĩ về một thành phần React tìm nạp một số dữ liệu và sau đó hiển thị một số khác Thành phần phản ứng, truyền dữ liệu. Điều này sẽ không thực sự hoạt động như một thành phần web, vì một thành phần web không biết cách hiển thị một thành phần React.

Các thành phần web đặc biệt nổi trội như thành phần lá. Thành phần lá là những thứ cuối cùng được hiển thị trong cây thành phần. Đây là những thành phần nhận một số đạo cụ và kết xuất một số UI. Đây là những không các thành phần nằm ở giữa cây thành phần của bạn, truyền dữ liệu cùng, thiết lập ngữ cảnh, v.v. - chỉ là những phần thuần túy của UI trông sẽ giống nhau, bất kể khung JavaScript nào đang cung cấp năng lượng cho phần còn lại của ứng dụng.

Thành phần web mà chúng tôi đang xây dựng

Thay vì xây dựng một cái gì đó nhàm chán (và phổ biến), như một cái nút, hãy xây dựng một cái gì đó khác biệt một chút. trong tôi bài cuối chúng tôi đã xem xét việc sử dụng bản xem trước hình ảnh mờ để ngăn nội dung chỉnh lại và cung cấp giao diện người dùng phù hợp cho người dùng trong khi tải hình ảnh của chúng tôi. Chúng tôi đã xem xét việc mã hóa base64 các phiên bản hình ảnh bị mờ, xuống cấp và hiển thị điều đó trong giao diện người dùng của chúng tôi khi hình ảnh thực được tải. Chúng tôi cũng đã xem xét việc tạo ra các bản xem trước cực kỳ nhỏ gọn, mờ ảo bằng cách sử dụng một công cụ có tên là má hồng.

Bài đăng đó đã chỉ cho bạn cách tạo các bản xem trước đó và sử dụng chúng trong một dự án React. Bài đăng này sẽ chỉ cho bạn cách sử dụng các bản xem trước đó từ một thành phần web để chúng có thể được sử dụng bởi bất kì Khung JavaScript.

Nhưng chúng ta cần phải đi bộ trước khi có thể chạy, vì vậy trước tiên chúng ta sẽ đi qua một số thứ tầm thường và ngớ ngẩn để xem chính xác cách các thành phần web hoạt động.

Mọi thứ trong bài đăng này sẽ xây dựng các thành phần web vani mà không cần bất kỳ công cụ nào. Điều đó có nghĩa là mã sẽ có một chút bản soạn sẵn, nhưng phải tương đối dễ làm theo. Các công cụ như Ánh sáng or Stt được thiết kế để xây dựng các thành phần web và có thể được sử dụng để loại bỏ phần lớn bảng tạo sẵn này. Tôi mong bạn kiểm tra chúng! Nhưng đối với bài đăng này, tôi sẽ thích một bản soạn sẵn hơn một chút để đổi lấy việc không phải giới thiệu và dạy thêm một phần phụ thuộc nào khác.

Một thành phần bộ đếm đơn giản

Hãy xây dựng “Hello World” cổ điển của các thành phần JavaScript: một bộ đếm. Chúng tôi sẽ hiển thị một giá trị và một nút tăng giá trị đó. Đơn giản và nhàm chán, nhưng nó sẽ cho chúng ta xem xét thành phần web đơn giản nhất có thể.

Để xây dựng một thành phần web, bước đầu tiên là tạo một lớp JavaScript, lớp này kế thừa từ HTMLElement:

class Counter extends HTMLElement {}

Bước cuối cùng là đăng ký thành phần web, nhưng chỉ khi chúng tôi chưa đăng ký thành phần đó:

if (!customElements.get("counter-wc")) { customElements.define("counter-wc", Counter);
}

Và, tất nhiên, kết xuất nó:

<counter-wc></counter-wc>

Và mọi thứ ở giữa là chúng ta làm cho thành phần web làm bất cứ điều gì chúng ta muốn. Một phương pháp vòng đời phổ biến là connectedCallback, kích hoạt khi thành phần web của chúng tôi được thêm vào DOM. Chúng tôi có thể sử dụng phương pháp đó để hiển thị bất kỳ nội dung nào chúng tôi muốn. Hãy nhớ rằng, đây là một lớp JS kế thừa từ HTMLElement, có nghĩa là của chúng tôi this value là bản thân phần tử thành phần web, với tất cả các phương pháp thao tác DOM thông thường mà bạn đã biết và yêu thích.

Nó đơn giản nhất, chúng tôi có thể làm điều này:

class Counter extends HTMLElement { connectedCallback() { this.innerHTML = "<div style='color: green'>Hey</div>"; }
} if (!customElements.get("counter-wc")) { customElements.define("counter-wc", Counter);
}

… Sẽ hoạt động tốt.

Từ "hey" màu xanh lá cây.
Xây dựng các thành phần web có thể tương tác thậm chí hoạt động với React

Thêm nội dung thực

Hãy thêm một số nội dung hữu ích, tương tác. Chúng tôi cần một <span> để giữ giá trị số hiện tại và <button> để tăng bộ đếm. Hiện tại, chúng tôi sẽ tạo nội dung này trong hàm tạo của chúng tôi và nối nó vào khi thành phần web thực sự nằm trong DOM:

constructor() { super(); const container = document.createElement('div'); this.valSpan = document.createElement('span'); const increment = document.createElement('button'); increment.innerText = 'Increment'; increment.addEventListener('click', () => { this.#value = this.#currentValue + 1; }); container.appendChild(this.valSpan); container.appendChild(document.createElement('br')); container.appendChild(increment); this.container = container;
} connectedCallback() { this.appendChild(this.container); this.update();
}

Nếu bạn thực sự thu được lợi nhuận từ việc tạo DOM thủ công, hãy nhớ rằng bạn có thể đặt innerHTMLhoặc thậm chí tạo một phần tử mẫu một lần dưới dạng thuộc tính tĩnh của lớp thành phần web của bạn, sao chép nó và chèn nội dung cho các trường hợp thành phần web mới. Có thể có một số tùy chọn khác mà tôi không nghĩ đến hoặc bạn luôn có thể sử dụng khung thành phần web như Ánh sáng or Stt. Nhưng đối với bài đăng này, chúng tôi sẽ tiếp tục giữ cho nó đơn giản.

Tiếp tục, chúng ta cần một thuộc tính lớp JavaScript có thể cài đặt có tên value

#currentValue = 0; set #value(val) { this.#currentValue = val; this.update();
}

Nó chỉ là một thuộc tính lớp tiêu chuẩn với một bộ thiết lập, cùng với một thuộc tính thứ hai để giữ giá trị. Một điều thú vị là tôi đang sử dụng cú pháp thuộc tính lớp JavaScript riêng cho các giá trị này. Điều đó có nghĩa là không ai bên ngoài thành phần web của chúng tôi có thể chạm vào các giá trị này. Đây là JavaScript chuẩn được hỗ trợ trong tất cả các trình duyệt hiện đại, vì vậy đừng ngại sử dụng nó.

Hoặc vui lòng gọi nó _value nếu bạn thích. Và cuối cùng, update phương pháp:

update() { this.valSpan.innerText = this.#currentValue;
}

Nó hoạt động!

Thành phần web truy cập.
Xây dựng các thành phần web có thể tương tác thậm chí hoạt động với React

Rõ ràng đây không phải là mã bạn muốn duy trì trên quy mô lớn. Đây là một đầy đủ ví dụ làm việc nếu bạn muốn xem xét kỹ hơn. Như tôi đã nói, các công cụ như Lit và Stencil được thiết kế để làm cho việc này trở nên đơn giản hơn.

Thêm một số chức năng khác

Bài đăng này không phải là một chuyên sâu về các thành phần web. Chúng tôi sẽ không đề cập đến tất cả các API và vòng đời; chúng tôi thậm chí sẽ không bao gồm rễ bóng hoặc khe. Có vô số nội dung về những chủ đề đó. Mục tiêu của tôi ở đây là cung cấp một phần giới thiệu đủ tốt để thu hút sự quan tâm, cùng với một số hướng dẫn hữu ích về thực sự sử dụng các thành phần web với các khung JavaScript phổ biến mà bạn đã biết và yêu thích.

Để đạt được điều đó, hãy nâng cao thành phần web truy cập của chúng tôi một chút. Hãy để nó chấp nhận một color để kiểm soát màu của giá trị được hiển thị. Và cũng hãy để nó chấp nhận một increment , vì vậy người tiêu dùng thành phần web này có thể có giá trị đó tăng lên 2, 3, 4 cùng một lúc. Và để thúc đẩy những thay đổi trạng thái này, hãy sử dụng bộ đếm mới của chúng tôi trong hộp cát Svelte - chúng ta sẽ chuyển sang React sau một chút.

Chúng tôi sẽ bắt đầu với cùng một thành phần web như trước và thêm một thuộc tính màu. Để định cấu hình thành phần web của chúng tôi để chấp nhận và phản hồi một thuộc tính, chúng tôi thêm một observedAttributes thuộc tính trả về các thuộc tính mà thành phần web của chúng tôi lắng nghe.

static observedAttributes = ["color"];

Với điều đó tại chỗ, chúng tôi có thể thêm một attributeChangedCallback phương thức vòng đời, sẽ chạy bất cứ khi nào bất kỳ thuộc tính nào được liệt kê trong observedAttributes được thiết lập hoặc cập nhật.

attributeChangedCallback(name, oldValue, newValue) { if (name === "color") { this.update(); }
}

Bây giờ chúng tôi cập nhật update phương pháp để thực sự sử dụng nó:

update() { this.valSpan.innerText = this._currentValue; this.valSpan.style.color = this.getAttribute("color") || "black";
}

Cuối cùng, hãy thêm increment bất động sản:

increment = 1;

Đơn giản và khiêm tốn.

Sử dụng thành phần bộ đếm trong Svelte

Hãy sử dụng những gì chúng ta vừa tạo ra. Chúng tôi sẽ đi vào thành phần ứng dụng Svelte của chúng tôi và thêm một cái gì đó như sau:

<script> let color = "red";
</script> <style> main { text-align: center; }
</style> <main> <select bind:value={color}> <option value="red">Red</option> <option value="green">Green</option> <option value="blue">Blue</option> </select> <counter-wc color={color}></counter-wc>
</main>

Và nó hoạt động! Bộ đếm của chúng tôi hiển thị, gia số và menu thả xuống cập nhật màu sắc. Như bạn có thể thấy, chúng tôi hiển thị thuộc tính màu trong mẫu Svelte của chúng tôi và khi giá trị thay đổi, Svelte xử lý công việc gọi setAttribute trên phiên bản thành phần web cơ bản của chúng tôi. Không có gì đặc biệt ở đây: đây là điều tương tự như nó đã làm cho các thuộc tính của bất kì Phần tử HTML.

Mọi thứ trở nên thú vị một chút với increment chỗ dựa. Đây là không một thuộc tính trên thành phần web của chúng tôi; nó là một chỗ dựa trên lớp của thành phần web. Điều đó có nghĩa là nó cần được đặt trên phiên bản của thành phần web. Hãy chịu đựng tôi, vì mọi thứ sẽ trở nên đơn giản hơn nhiều.

Đầu tiên, chúng tôi sẽ thêm một số biến vào thành phần Svelte của chúng tôi:

let increment = 1;
let wcInstance;

Sức mạnh của thành phần bộ đếm của chúng tôi sẽ cho phép bạn tăng thêm 1 hoặc 2:

<button on:click={() => increment = 1}>Increment 1</button>
<button on:click={() => increment = 2}>Increment 2</button>

Nhưng, trên lý thuyết, chúng tôi cần lấy phiên bản thực tế của thành phần web của chúng tôi. Đây cũng là điều chúng tôi luôn làm bất cứ khi nào chúng tôi thêm một ref với React. Với Svelte, thật đơn giản bind:this chỉ thị:

<counter-wc bind:this={wcInstance} color={color}></counter-wc>

Bây giờ, trong mẫu Svelte của chúng tôi, chúng tôi lắng nghe các thay đổi đối với biến tăng dần của thành phần của chúng tôi và đặt thuộc tính thành phần web bên dưới.

$: { if (wcInstance) { wcInstance.increment = increment; }
}

Bạn có thể kiểm tra nó ra kết thúc buổi giới thiệu trực tiếp này.

Rõ ràng là chúng tôi không muốn làm điều này cho mọi thành phần hoặc phần hỗ trợ web mà chúng tôi cần quản lý. Sẽ không tốt nếu chúng ta chỉ có thể thiết lập increment ngay trên thành phần web của chúng tôi, trong phần đánh dấu, giống như chúng tôi thường làm đối với các đạo cụ thành phần và có nó, bạn biết đấy, chỉ làm việc? Nói cách khác, sẽ rất tuyệt nếu chúng ta có thể xóa tất cả các tập quán của wcInstance và sử dụng mã đơn giản hơn này để thay thế:

<counter-wc increment={increment} color={color}></counter-wc>

Hóa ra là chúng ta có thể. Mã này hoạt động; Svelte xử lý tất cả công việc đó cho chúng tôi. Kiểm tra nó trong bản demo này. Đây là hành vi tiêu chuẩn cho hầu hết các khung JavaScript.

Vậy tại sao tôi lại chỉ cho bạn cách thủ công để thiết lập chỗ dựa của thành phần web? Hai lý do: thật hữu ích khi hiểu cách những thứ này hoạt động và, một lúc trước, tôi đã nói rằng điều này hoạt động cho “khá nhiều” tất cả các khung JavaScript. Nhưng có một khung công tác, thật đáng buồn, không hỗ trợ cài đặt hỗ trợ thành phần web như chúng ta vừa thấy.

React là một con thú khác

Xây dựng các thành phần web có thể tương tác thậm chí hoạt động với React PlatoBlockchain Data Intelligence. Tìm kiếm dọc. Ái.
Xây dựng các thành phần web có thể tương tác thậm chí hoạt động với React

Phản ứng. Khung JavaScript phổ biến nhất trên hành tinh không hỗ trợ tương tác cơ bản với các thành phần web. Đây là một vấn đề nổi tiếng chỉ có ở React. Thật thú vị, điều này thực sự đã được sửa trong nhánh thử nghiệm của React, nhưng vì một số lý do không được hợp nhất vào phiên bản 18. Điều đó nói rằng, chúng ta vẫn có thể theo dõi tiến trình của nó. Và bạn có thể tự mình thử điều này với demo sống.

Tất nhiên, giải pháp là sử dụng ref, lấy phiên bản thành phần web và đặt theo cách thủ công increment khi giá trị đó thay đổi. Nó trông như thế này:

import React, { useState, useRef, useEffect } from 'react';
import './counter-wc'; export default function App() { const [increment, setIncrement] = useState(1); const [color, setColor] = useState('red'); const wcRef = useRef(null); useEffect(() => { wcRef.current.increment = increment; }, [increment]); return ( <div> <div className="increment-container"> <button onClick={() => setIncrement(1)}>Increment by 1</button> <button onClick={() => setIncrement(2)}>Increment by 2</button> </div> <select value={color} onChange={(e) => setColor(e.target.value)}> <option value="red">Red</option> <option value="green">Green</option> <option value="blue">Blue</option> </select> <counter-wc ref={wcRef} increment={increment} color={color}></counter-wc> </div> );
}

Như chúng ta đã thảo luận, mã hóa điều này theo cách thủ công cho mọi thuộc tính thành phần web đơn giản là không thể mở rộng. Nhưng tất cả không bị mất bởi vì chúng tôi có một vài lựa chọn.

Tùy chọn 1: Sử dụng thuộc tính ở mọi nơi

Chúng tôi có các thuộc tính. Nếu bạn nhấp vào bản demo React ở trên, increment prop không hoạt động, nhưng màu sắc đã thay đổi chính xác. Chúng ta không thể mã hóa mọi thứ bằng các thuộc tính? Thật đáng buồn không. Giá trị thuộc tính chỉ có thể là chuỗi. Ở đây là đủ tốt và chúng tôi sẽ có thể tiến xa hơn với cách tiếp cận này. Những con số như increment có thể được chuyển đổi sang và từ chuỗi. Chúng tôi thậm chí có thể JSON xâu chuỗi / phân tích cú pháp các đối tượng. Nhưng cuối cùng chúng ta sẽ cần phải chuyển một hàm vào một thành phần web và tại thời điểm đó, chúng ta sẽ hết các tùy chọn.

Tùy chọn 2: Kết thúc

Có một câu nói cũ rằng bạn có thể giải quyết bất kỳ vấn đề nào trong khoa học máy tính bằng cách thêm một cấp độ chuyển hướng (ngoại trừ vấn đề có quá nhiều cấp độ chuyển hướng). Mã để đặt các đạo cụ này khá dễ đoán và đơn giản. Điều gì sẽ xảy ra nếu chúng ta giấu nó trong thư viện? Những người thông minh đằng sau Lit có một giải pháp. Thư viện này tạo một thành phần React mới cho bạn sau khi bạn cung cấp cho nó một thành phần web và liệt kê các thuộc tính mà nó cần. Mặc dù thông minh, tôi không phải là một fan hâm mộ của cách tiếp cận này.

Thay vì có một ánh xạ XNUMX-XNUMX giữa các thành phần web với các thành phần React được tạo thủ công, những gì tôi thích chỉ là một Thành phần phản ứng mà chúng tôi chuyển thành phần web của mình tên thẻ đến (counter-wc trong trường hợp của chúng tôi) - cùng với tất cả các thuộc tính và thuộc tính - và để thành phần này hiển thị thành phần web của chúng tôi, hãy thêm ref, sau đó tìm ra đâu là chỗ dựa và đâu là thuộc tính. Đó là giải pháp lý tưởng theo quan điểm của tôi. Tôi không biết có thư viện nào làm được điều này, nhưng bạn nên tạo một cách đơn giản. Hãy thử xem!

Đây là sử dụng chúng tôi đang tìm kiếm:

<WcWrapper wcTag="counter-wc" increment={increment} color={color} />

wcTag là tên thẻ thành phần web; phần còn lại là các thuộc tính và thuộc tính mà chúng tôi muốn được chuyển cùng.

Đây là cách triển khai của tôi trông như thế nào:

import React, { createElement, useRef, useLayoutEffect, memo } from 'react'; const _WcWrapper = (props) => { const { wcTag, children, ...restProps } = props; const wcRef = useRef(null); useLayoutEffect(() => { const wc = wcRef.current; for (const [key, value] of Object.entries(restProps)) { if (key in wc) { if (wc[key] !== value) { wc[key] = value; } } else { if (wc.getAttribute(key) !== value) { wc.setAttribute(key, value); } } } }); return createElement(wcTag, { ref: wcRef });
}; export const WcWrapper = memo(_WcWrapper);

Dòng thú vị nhất ở cuối:

return createElement(wcTag, { ref: wcRef });

Đây là cách chúng ta tạo một phần tử trong React với tên động. Trên thực tế, đây là những gì React thường chuyển JSX thành. Tất cả các div của chúng tôi được chuyển đổi thành createElement("div") cuộc gọi. Thông thường chúng ta không cần gọi trực tiếp API này nhưng nó ở đó khi chúng ta cần.

Ngoài ra, chúng tôi muốn chạy một hiệu ứng bố cục và lặp lại mọi hỗ trợ mà chúng tôi đã chuyển cho thành phần của mình. Chúng tôi lặp lại tất cả chúng và kiểm tra xem đó có phải là thuộc tính có in kiểm tra kiểm tra đối tượng cá thể thành phần web cũng như chuỗi nguyên mẫu của nó, điều này sẽ bắt bất kỳ getters / setters nào kết thúc trên nguyên mẫu lớp. Nếu không có thuộc tính nào như vậy tồn tại, nó được giả định là một thuộc tính. Trong cả hai trường hợp, chúng tôi chỉ đặt nó nếu giá trị đã thực sự thay đổi.

Nếu bạn đang thắc mắc tại sao chúng tôi sử dụng useLayoutEffect thay vì useEffect, đó là vì chúng tôi muốn chạy ngay các bản cập nhật này trước khi nội dung của chúng tôi được hiển thị. Ngoài ra, lưu ý rằng chúng tôi không có mảng phụ thuộc vào useLayoutEffect; điều này có nghĩa là chúng tôi muốn chạy bản cập nhật này trên mọi kết xuất. Điều này có thể rủi ro vì React có xu hướng kết xuất lại rất nhiều. Tôi cải thiện điều này bằng cách gói toàn bộ vào React.memo. Đây thực chất là phiên bản hiện đại của React.PureComponent, có nghĩa là thành phần sẽ chỉ hiển thị lại nếu bất kỳ đạo cụ thực tế nào của nó đã thay đổi - và nó kiểm tra xem điều đó có xảy ra hay không thông qua một kiểm tra bình đẳng đơn giản.

Rủi ro duy nhất ở đây là nếu bạn đang chuyển một đối tượng chống đỡ mà bạn đang đột biến trực tiếp mà không chỉ định lại, thì bạn sẽ không thấy các bản cập nhật. Nhưng điều này rất không được khuyến khích, đặc biệt là trong cộng đồng React, vì vậy tôi sẽ không lo lắng về nó.

Trước khi tiếp tục, tôi muốn nói một điều cuối cùng. Bạn có thể không hài lòng với cách sử dụng trông như thế nào. Một lần nữa, thành phần này được sử dụng như thế này:

<WcWrapper wcTag="counter-wc" increment={increment} color={color} />

Cụ thể, bạn có thể không muốn chuyển tên thẻ thành phần web cho <WcWrapper> thành phần và thay vào đó thích @lit-labs/react gói ở trên, tạo một thành phần React riêng lẻ mới cho từng thành phần web. Điều đó hoàn toàn công bằng và tôi khuyến khích bạn sử dụng bất cứ thứ gì bạn cảm thấy thoải mái nhất. Nhưng đối với tôi, một lợi thế với cách tiếp cận này là nó dễ dàng xóa. Nếu bằng một phép màu nào đó, React hợp nhất việc xử lý thành phần web thích hợp từ nhánh thử nghiệm của họ thành main ngày mai, bạn có thể thay đổi mã trên từ mã này:

<WcWrapper wcTag="counter-wc" increment={increment} color={color} />

… Đến đây:

<counter-wc ref={wcRef} increment={increment} color={color} />

Bạn thậm chí có thể viết một codemod duy nhất để làm điều đó ở mọi nơi, và sau đó xóa <WcWrapper> hoàn toàn. Trên thực tế, hãy nhớ rằng: một tìm kiếm toàn cầu và thay thế bằng RegEx có thể sẽ hoạt động.

Việc thực hiện

Tôi biết, có vẻ như nó đã mất một cuộc hành trình để đến được đây. Nếu bạn nhớ lại, mục tiêu ban đầu của chúng tôi là lấy mã xem trước hình ảnh mà chúng tôi đã xem trong bài cuốivà di chuyển nó vào một thành phần web để nó có thể được sử dụng trong bất kỳ khung JavaScript nào. Việc thiếu tương tác thích hợp của React đã thêm rất nhiều chi tiết vào hỗn hợp. Nhưng bây giờ chúng ta đã nắm rõ được cách tạo một thành phần web và sử dụng nó, việc triển khai gần như sẽ chống lại sự cao trào.

Tôi sẽ thả toàn bộ thành phần web ở đây và gọi ra một số điểm thú vị. Nếu bạn muốn thấy nó hoạt động, đây là bản demo làm việc. Nó sẽ chuyển đổi giữa ba cuốn sách yêu thích của tôi trên ba ngôn ngữ lập trình yêu thích của tôi. URL cho mỗi cuốn sách sẽ là duy nhất mỗi lần, vì vậy bạn có thể xem bản xem trước, mặc dù bạn có thể muốn điều chỉnh mọi thứ trong tab Mạng DevTools của mình để thực sự thấy mọi thứ đang diễn ra.

Xem toàn bộ mã
class BookCover extends HTMLElement { static observedAttributes = ['url']; attributeChangedCallback(name, oldValue, newValue) { if (name === 'url') { this.createMainImage(newValue); } } set preview(val) { this.previewEl = this.createPreview(val); this.render(); } createPreview(val) { if (typeof val === 'string') { return base64Preview(val); } else { return blurHashPreview(val); } } createMainImage(url) { this.loaded = false; const img = document.createElement('img'); img.alt = 'Book cover'; img.addEventListener('load', () =&gt; { if (img === this.imageEl) { this.loaded = true; this.render(); } }); img.src = url; this.imageEl = img; } connectedCallback() { this.render(); } render() { const elementMaybe = this.loaded ? this.imageEl : this.previewEl; syncSingleChild(this, elementMaybe); }
}

Đầu tiên, chúng tôi đăng ký thuộc tính mà chúng tôi quan tâm và phản ứng khi nó thay đổi:

static observedAttributes = ['url']; attributeChangedCallback(name, oldValue, newValue) { if (name === 'url') { this.createMainImage(newValue); }
}

Điều này khiến thành phần hình ảnh của chúng tôi được tạo, thành phần này sẽ chỉ hiển thị khi được tải:

createMainImage(url) { this.loaded = false; const img = document.createElement('img'); img.alt = 'Book cover'; img.addEventListener('load', () => { if (img === this.imageEl) { this.loaded = true; this.render(); } }); img.src = url; this.imageEl = img;
}

Tiếp theo, chúng tôi có thuộc tính xem trước của mình, có thể là chuỗi xem trước base64 của chúng tôi hoặc blurhash gói:

set preview(val) { this.previewEl = this.createPreview(val); this.render();
} createPreview(val) { if (typeof val === 'string') { return base64Preview(val); } else { return blurHashPreview(val); }
}

Điều này liên quan đến bất kỳ chức năng trợ giúp nào chúng ta cần:

function base64Preview(val) { const img = document.createElement('img'); img.src = val; return img;
} function blurHashPreview(preview) { const canvasEl = document.createElement('canvas'); const { w: width, h: height } = preview; canvasEl.width = width; canvasEl.height = height; const pixels = decode(preview.blurhash, width, height); const ctx = canvasEl.getContext('2d'); const imageData = ctx.createImageData(width, height); imageData.data.set(pixels); ctx.putImageData(imageData, 0, 0); return canvasEl;
}

Và cuối cùng, render phương pháp:

connectedCallback() { this.render();
} render() { const elementMaybe = this.loaded ? this.imageEl : this.previewEl; syncSingleChild(this, elementMaybe);
}

Và một số phương pháp trợ giúp để gắn kết mọi thứ với nhau:

export function syncSingleChild(container, child) { const currentChild = container.firstElementChild; if (currentChild !== child) { clearContainer(container); if (child) { container.appendChild(child); } }
} export function clearContainer(el) { let child; while ((child = el.firstElementChild)) { el.removeChild(child); }
}

Nó nhiều hơn một chút so với những gì chúng ta cần nếu chúng ta xây dựng cái này trong một khuôn khổ, nhưng ưu điểm là chúng ta có thể sử dụng lại cái này trong bất kỳ khuôn khổ nào chúng ta muốn - mặc dù hiện tại React sẽ cần một trình bao bọc, như chúng ta đã thảo luận .

Vụn vặt

Tôi đã đề cập đến trình bao bọc React của Lit. Nhưng nếu bạn thấy mình đang sử dụng Stencil, nó thực sự hỗ trợ đường ống đầu ra riêng biệt chỉ dành cho React. Và những người giỏi ở Microsoft cũng có đã tạo thứ gì đó tương tự như trình bao bọc của Lit, được đính kèm vào thư viện thành phần web Nhanh.

Như tôi đã đề cập, tất cả các khung công tác không có tên React sẽ xử lý việc thiết lập các thuộc tính thành phần web cho bạn. Chỉ cần lưu ý rằng một số có một số hương vị đặc biệt của cú pháp. Ví dụ, với Solid.js, <your-wc value={12}> luôn giả định rằng value là một thuộc tính mà bạn có thể ghi đè lên attr tiền tố, như <your-wc attr:value={12}>.

Kết thúc

Các thành phần web là một phần thú vị, thường được sử dụng ít trong bối cảnh phát triển web. Chúng có thể giúp giảm sự phụ thuộc của bạn vào bất kỳ khung JavaScript đơn lẻ nào bằng cách quản lý giao diện người dùng hoặc các thành phần “lá” của bạn. Mặc dù việc tạo ra những thứ này dưới dạng các thành phần web - trái ngược với các thành phần Svelte hoặc React - sẽ không tiện lợi như vậy, nhưng ưu điểm là chúng sẽ có thể tái sử dụng rộng rãi.


Xây dựng các thành phần web có thể tương tác thậm chí hoạt động với React ban đầu được xuất bản trên CSS-Thủ thuật. Bạn nên nhận bản tin.

Dấu thời gian:

Thêm từ Thủ thuật CSS