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