【译】 Web Components 的高级工具
- 原文地址:Advanced Tooling for Web Components
- 原文作者:Caleb Williams
- 譯文出自:掘金翻譯計劃
- 本文永久鏈接:github.com/xitu/gold-m…
- 譯者:Xuyuey
- 校對者:Long Xiong, Ziyin Feng
該系列由 5 篇文章構成,我們在前 4 篇文章中對構成 Web Components 標準的技術進行了全面的介紹。首先,我們研究了如何創建 HTML 模板,為接下來的工作做了鋪墊。其次,我們深入了解了自定義元素的創建。接著,我們將元素的樣式和選擇器封裝到 shadow DOM 中,這樣我們的元素就完全獨立了。
我們通過創建自己的自定義模態對話框來探索這些工具的強大功能,該對話框可以忽略底層框架或庫,在大多數現代應用程序上下文中使用。在本文中,我們將介紹如何在各種框架中使用我們的元素,并介紹一些高級工具用來真正提高 Web Component 的技能。
系列文章:
框架兼容
我們的對話框組件幾乎在任何框架中都可以很好地運行。(當然,如果 JavaScript 被禁用,那么整個事情都是徒勞的。)Angular 和 Vue 將 Web Components 視為一等公民:框架的設計考慮了 Web 標準。React 稍微有點自以為是,但并非不可以整合。
Angular
首先,我們來看看 Angular 如何處理自定義元素。默認情況下,每當 Angular 遇到無法識別的元素(即默認瀏覽器元素或任何 Angular 定義的組件),它就會拋出模板錯誤。可以通過包含 CUSTOM_ELEMENTS_SCHEMA 來更改這個行為。
...允許 NgModule 包含以下內容:
- Non-Angular 元素用破折號(-)命名。
- 元素屬性用破折號(-)命名。破折號是自定義元素的命名約定。
— Angular 文檔
使用此架構就像在模塊中添加它一樣簡單:
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';@NgModule({/** 省略 */schemas: [ CUSTOM_ELEMENTS_SCHEMA ] }) export class MyModuleAllowsCustomElements {} 復制代碼就像上面這樣。之后,Angular 將允許我們在任何使用標準屬性和綁定事件的地方使用我們的自定義元素:
<one-dialog [open]="isDialogOpen" (dialog-closed)="dialogClosed($event)"><span slot="heading">Heading text</span><div><p>Body copy</p></div> </one-dialog> 復制代碼Vue
Vue 對 Web Components 的兼容性甚至比 Angular 更好,因為它不需要任何特殊配置。注冊元素后,它可以與 Vue 的默認模板語法一起使用:
<one-dialog v-bind:open="isDialogOpen" v-on:dialog-closed="dialogClosed"><span slot="heading">Heading text</span><div><p>Body copy</p></div> </one-dialog> 復制代碼然而,Angular 和 Vue 都需要注意的是它們的默認表單控件。如果我們希望使用一個類似于可響應的表單或者 Angular 的 [(ng-model)] 或者 Vue 中的 v-model 的東西,我們需要建立管道,這個超出了本篇文章的討論范圍。
React
React 比 Angular 稍微復雜一點。React 的虛擬 DOM 有效地獲取了一個 JSX 樹并將其渲染為一個大對象。因此,React 不是像 Angular 或 Vue 一樣,直接修改 HTML 元素上的屬性,而是使用對象語法來跟蹤需要對 DOM 進行的更改并批量更新它們。在大多數情況下這很好用。我們將對話框的 open 屬性綁定到對象的屬性上,在改變屬性時響應非常好。
當我們關閉對話框,開始調度 CustomEvent 時,會出現問題。React 使用合成事件系統為我們實現了一系列原生事件監聽器。不幸的是,這意味著像 onDialogClosed 這樣的控制方法實際上不會將事件監聽器附加到我們的組件上,因此我們必須找到其他方法。
在 React 中添加自定義事件監聽器的最著名的方法是使用 DOM refs。在這個模型中,我們可以直接引用我們的 HTML 節點。語法有點冗長,但效果很好:
import React, { Component, createRef } from 'react';export default class MyComponent extends Component {constructor(props) {super(props);// 創建引用this.dialog = createRef();// 在實例上綁定我們的方法this.onDialogClosed = this.onDialogClosed.bind(this);this.state = {open: false};}componentDidMount() {// 組件構建完成后,添加事件監聽器this.dialog.current.addEventListener('dialog-closed', this.onDialogClosed);}componentWillUnmount() {// 卸載組件時,刪除監聽器this.dialog.current.removeEventListener('dialog-closed', this.onDialogClosed);}onDialogClosed(event) { /** 省略 **/ }render() {return <div><one-dialog open={this.state.open} ref={this.dialog}><span slot="heading">Heading text</span><div><p>Body copy</p></div></one-dialog></div>} } 復制代碼或者,我們可以使用無狀態函數組件和鉤子:
import React, { useState, useEffect, useRef } from 'react';export default function MyComponent(props) {const [ dialogOpen, setDialogOpen ] = useState(false);const oneDialog = useRef(null);const onDialogClosed = event => console.log(event);useEffect(() => {oneDialog.current.addEventListener('dialog-closed', onDialogClosed);return () => oneDialog.current.removeEventListener('dialog-closed', onDialogClosed)});return <div><button onClick={() => setDialogOpen(true)}>Open dialog</button><one-dialog ref={oneDialog} open={dialogOpen}><span slot="heading">Heading text</span><div><p>Body copy</p></div></one-dialog></div> } 復制代碼這個還不錯,但你可以看到重用這個組件很快會變得很麻煩。幸運的是,我們可以導出一個默認的 React 組件,它使用相同的工具包裹我們的自定義元素。
import React, { Component, createRef } from 'react'; import PropTypes from 'prop-types';export default class OneDialog extends Component {constructor(props) {super(props);// 創建引用this.dialog = createRef();// 在實例上綁定我們的方法this.onDialogClosed = this.onDialogClosed.bind(this);}componentDidMount() {// 組件構建完成后,添加事件監聽器this.dialog.current.addEventListener('dialog-closed', this.onDialogClosed);}componentWillUnmount() {// 卸載組件時,刪除監聽器this.dialog.current.removeEventListener('dialog-closed', this.onDialogClosed);}onDialogClosed(event) {// 在調用屬性之前進行檢查以確保它是存在的if (this.props.onDialogClosed) {this.props.onDialogClosed(event);}}render() {const { children, onDialogClosed, ...props } = this.props;return <one-dialog {...props} ref={this.dialog}>{children}</one-dialog>} }OneDialog.propTypes = {children: children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node),PropTypes.node]).isRequired,onDialogClosed: PropTypes.func }; 復制代碼...或者,再次使用無狀態函數組件和鉤子:
import React, { useRef, useEffect } from 'react'; import PropTypes from 'prop-types';export default function OneDialog(props) {const { children, onDialogClosed, ...restProps } = props;const oneDialog = useRef(null);useEffect(() => {onDialogClosed ? oneDialog.current.addEventListener('dialog-closed', onDialogClosed) : null;return () => {onDialogClosed ? oneDialog.current.removeEventListener('dialog-closed', onDialogClosed) : null; };});return <one-dialog ref={oneDialog} {...restProps}>{children}</one-dialog> } 復制代碼現在我們可以在 React 中使用我們的對話框,而且可以在我們所有的應用程序中保持相同的 API(如果你喜歡的話,還可以不使用類)。
import React, { useState } from 'react'; import OneDialog from './OneDialog';export default function MyComponent(props) {const [open, setOpen] = useState(false);return <div><button onClick={() => setOpen(true)}>Open dialog</button><OneDialog open={open} onDialogClosed={() => setOpen(false)}><span slot="heading">Heading text</span><div><p>Body copy</p></div></OneDialog></div> } 復制代碼高級工具
有很多非常棒的工具可以用來編寫你的自定義元素。在 npm 上進行搜索,你能找到許多用于創建高響應性自定義元素的工具(包括我自己的寵物項目),但到目前為止最流行的是來自 Polymer 團隊的 lit-html,對 Web Components 來說更具體的是指,LitElement。
LitElement 是一個自定義元素基類,它提供了一系列 API,可以用于完成我們迄今為止所做的所有事情。不用構建它也可以在瀏覽器中運行,但如果你喜歡使用更前沿的工具,如裝飾器,那么也可以使用它。
在深入了解如何使用 lit 或 LitElement 之前,請花一點時間熟悉 帶標簽的模板字符串(tagged template literals),這是一種特殊的函數,可以在 JavaScript 中調用模板字符串。這些函數接受一個字符串數組和一組內插值,并可以返回你可能想要的任何內容。
function tag(strings, ...values) {console.log({ strings, values });return true; } const who = 'world';tag`hello ${who}`; /** 會打印出 { strings: ['hello ', ''], values: ['world'] },并且返回 true **/ 復制代碼LitElement 為我們提供的是對傳遞給該值數組的任何內容的實時動態更新,因此當屬性更新時,將調用元素的 render 函數并重新渲染呈現 DOM。
import { LitElement, html } from 'lit-element';class SomeComponent {static get properties() {return { now: { type: String }};}connectedCallback() {// 一定要調用 supersuper.connectedCallback();this.interval = window.setInterval(() => {this.now = Date.now();});}disconnectedCallback() {super.disconnectedCallback();window.clearInterval(this.interval);}render() {return html`<h1>It is ${this.now}</h1>`;} }customElements.define('some-component', SomeComponent); 復制代碼在 CodePen 查看 LitElement 示例。
你會注意到我們必須使用 static properties getter 定義任何我們想要 LitElement 監視的屬性。使用該 API 會告訴基類每當對組件的屬性進行更改時都要調用 render 函數。反過來,render 將僅更新需要更改的節點。
因此,對于我們的對話框示例,它使用 LitElement 時看起來像這樣:
在 CodePen 查看 使用 LitElement 的對話框示例。
有幾種可用的 lit-html 的變體,包括 Haunted,一個用于 Web Components 的 React 鉤子庫,也可以使用 lit-html 作為基礎來使用虛擬組件。
目前,大多數現代 Web Components 工具都是 LitElement 的風格:一個從我們的組件中抽象出通用邏輯的基類。其他類型的有 Stencil、SkateJS、Angular Elements 和 Polymer。
下一步
Web Components 標準不斷發展,越來越多的新功能經過討論并被添加到瀏覽器中。很快,Web Components 的使用者將擁有用于與 Web 表單進行高級交互的 API(包括超出這些介紹性文章范圍的其他元素內部),例如原生 HTML 和 CSS 模塊導入,原生模板實例化和更新控件,更多的可以在 GitHub 上的 W3C/web components issues board on GitHub 進行跟蹤。
這些標準已經準備好應用到我們今天的項目中,并為舊版瀏覽器和 Edge 提供適當的 polyfill。雖然它們可能無法取代你選擇的框架,但它們可以一起使用,以增強你和你的團隊的工作流程。
系列文章:
如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改并 PR,也可獲得相應獎勵積分。文章開頭的 本文永久鏈接 即為本文在 GitHub 上的 MarkDown 鏈接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、后端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。
轉載于:https://juejin.im/post/5caef9f25188251b2b20b20b
總結
以上是生活随笔為你收集整理的【译】 Web Components 的高级工具的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 云计算的下半场:云原生
- 下一篇: VANSI致力成为全球优质数字资产交易平