在过去的三周里,我将我的 Web 应用程序从经典的 HTML+CSS+JS 切换到 Web 组件。一开始都是玫瑰。很好用:1. 封装,2. 重用,3. 编写的 JavaScript 代码更少,4. 重构一个,重构全部,5. UI 干净漂亮,6. 易于定位和分组。等等
当我对它们感到兴奋时,我开始将所有内容更改为 Web 组件,并将一个组件嵌套在另一个组件中...然后突然变成了一个真正的地狱...为了使组件彼此交互,还有很多工作要做其他,在 ShadowRoot 中使用我的 API,等等......最后我设法让它们一起工作。
这是我的弹出窗口之一的示例:
<s-popup id="userPanelPOP" placeholder="Painel do Usuário">
<div class="flexCenter" loading="lazy">
<div class="popupPageExt pdg10">
<!-- User Panel Header -->
<div class="w100 flex" style="height: 65px;">
<!-- ProfilePicture Container -->
<div class="h100 w100 flexCenter" id="profilePictureUserPanelBOX">
<div class="rel">
<svg-hexed-image class="profilePic"></svg-hexed-image>
<div class="abs changePhotoBox">
<div class="rel profilePhotoIconBanner">
<i class="bi bi-camera-fill abs profilePhotoIcon"></i>
<input class="inputFile" type="file" onchange="uploadProfilePicture(this)">
</div>
</div>
</div>
</div>
<!-- Status Container -->
<div class="h100 w100 flexCenter" id="statusUserPanelBOX">
<div class="balloon" id="userPanelAuthBox">
<i class="bi bi-exclamation-octagon-fill userPanelMenssager"></i>
</div>
</div>
<!-- Logout Container -->
<div class="h100 w100 rel">
<s-button class="abs posTR" id="logoutUserPanelBTN" model="danger" height="30px">
<div class="centerItemsH">
<i class="bi bi-box-arrow-left"></i>
<div style="min-width: 4px;"></div>
Sair
<div style="min-width: 1px;"></div>
</div>
</s-button>
</div>
</div>
<!-- Welcome Menssage -->
<s-text class="mgnTB10" id="welcomeUserPanelTXT"></s-text>
<!-- Selling / Buying -->
<div class="w100 flex mediaW380InlineBOX">
<!-- My Store -->
<div class="flexCenter flexRight w100">
<s-button class="w100" id="myStoreUserPanelBTN" model="disable">
<div class="centerItemsH">
<i class="bi bi-shop fs24"></i>
<div class="w10"></div>
Minha Loja
</div>
</s-button>
</div>
<!-- Divisor -->
<div class="mediaW380InlineDIV" style="min-width:10px;"></div>
<!-- My Purchases Historic -->
<div class="flexCenter flexLeft w100">
<s-button class="w100" id="myPurchasesUserPanelBTN">
<div class="centerItemsH">
<i class="bi bi-clock-history fs24"></i>
<div class="w10"></div>
Minhas Compras
</div>
</s-button>
</div>
</div>
<!-- User Informations -->
<s-topic class="mgnT10" id="userInformationsTPC" icon="bi-arrow-down-right-square" placeholder="Informações do Usuário" display="none" style="min-width:212px">
<div class="popupPage">
<s-input
class="mgnT5"
id="nameUserPanelINP"
placeholder="Nome Completo:"
maxlength="80"
charCounter="true"
model="edit"
regexAllow="FULL_NAME"
>Nome Completo</s-input>
<s-input
id="emailUserPanelINP"
placeholder="Email:"
inputmode="email"
maxlength="80"
charCounter="true"
model="edit"
regexAllow="EMAIL"
>Email</s-input>
<div class="flex">
<div class="w100">
<s-input
id="birthUserPanelINP"
placeholder="Data de Nascimento:"
inputmode="numeric"
maxlength="10"
model="edit"
regexAllow="BIRTH"
>Data de Nascimento</s-input>
</div>
<div class="mw10"></div>
<div class="w100">
<s-input
id="telefonUserPanelINP"
placeholder="Telefone:"
inputmode="numeric"
maxlength="14"
model="edit"
regexAllow="TELEFON"
>Telefone</s-input>
</div>
</div>
<!-- Gender Row -->
<div class="flex">
<div class="w100">
<s-input
id="cpfUserPanelINP"
placeholder="CPF:"
model="edit"
maxlength="14"
inputmode="numeric"
regexAllow="CPF"
>CPF</s-input>
</div>
<div class="mw10"></div>
<div class="w100">
<s-input
id="passwordUserPanelINP"
placeholder="Nova Senha:"
model="edit"
maxlength="60"
>Senha</s-input>
</div>
</div>
<s-input
class="disNone"
id="firstPasswordUserPanelINP"
placeholder="Primeira Senha:"
maxlength="60"
type="password"
>Primeira Senha</s-input>
<s-input
class="disNone"
id="newPassword1UserPanelINP"
placeholder="Nova Senha:"
maxlength="60"
type="password"
>Nova Senha</s-input>
<s-input
class="disNone"
id="newPassword2UserPanelINP"
placeholder="Nova Senha:"
maxlength="60"
type="password"
>Nova Senha</s-input>
<div class="flex">
<div class="w100 flexCenter">
<s-checkbox class="mgn10" id="womanUserPanelCB" onclick="tagGenderUserPanel(this)" group="true">Mulher</s-checkbox>
</div>
<div class="w100 flexCenter">
<s-checkbox class="mgn10" id="manUserPanelCB" onclick="tagGenderUserPanel(this)" group="true">Homem</s-checkbox>
</div>
<div class="w100 flexCenter">
<s-checkbox class="mgn10" id="otherUserPanelCB" onclick="tagGenderUserPanel(this)" group="true">Outro</s-checkbox>
</div>
</div>
<!-- User Addresses -->
<s-topic id="userAddressesTPC" icon="bi-arrow-down-right-square" placeholder="Endereços" display="none">
<div class="popupPage">
<s-box placeholder="Novo Endereço" id="newAddressBOX">
<s-topic class="mgnT5" id="closeNewAddressUserPanelTPC" icon="bi-dash-square-dotted" placeholder="Excluir Novo Endereço">
<s-input
id="nameNewAddressUserPanelINP"
placeholder="Nome do Endereço (Ex.:Casa):"
maxlength="80"
charCounter="true"
regexAllow="FULL_NAME"
>Email</s-input>
<div class="flex">
<div class="w100">
<s-input
id="cepNewAddressUserPanelINP"
placeholder="CEP:"
inputmode="numeric"
maxlength="10"
regexAllow="CPF"
>CEP</s-input>
</div>
<div class="mw10"></div>
<div class="w100">
<s-input
id="stNumberNewAddressUserPanelINP"
placeholder="Nº do Endereço:"
inputmode="numeric"
maxlength="7"
regexAllow="NUMBER"
>Nº do Endereço</s-input>
</div>
</div>
<s-input
id="streetNewAddressUserPanelINP"
placeholder="Rua:"
maxlength="80"
charCounter="true"
regexAllow="FULL_NAME"
>Rua</s-input>
<s-input
id="districtNewAddressUserPanelINP"
placeholder="Bairro:"
maxlength="80"
charCounter="true"
regexAllow="FULL_NAME"
>Bairro</s-input>
<div class="flex w100">
<div class="w100">
<s-input
id="cityNewAddressUserPanelINP"
placeholder="Cidade:"
maxlength="40"
regexAllow="FULL_NAME"
>CEP</s-input>
</div>
<div class="mw10"></div>
<div style="max-width:60px;min-width:60px;">
<s-input
id="stateNewAddressUserPanelINP"
maxlength="2"
regexAllow="FULL_NAME"
>Estado</s-input>
</div>
</div>
<s-input
id="complementNewAddressUserPanelINP"
placeholder="Complemento (opcional):"
maxlength="80"
charCounter="true"
regexAllow="FULL_NAME"
>Complemento</s-input>
<s-button class="w100 mgnT10" id="submitNewAddressUserPanelBTN" model="disable">Cadastrar Novo Endereço</s-button>
</s-box>
<s-topic class="mgnT10" id="addNewAddressUserPanelTPC" icon="bi-plus-square-dotted" placeholder="Adicionar Endereço"></s-topic>
</div>
</s-topic>
</div>
</s-topic>
<s-input
class="disNone"
id="actualPasswordUserPanelINP"
placeholder="Senha Atual:"
maxlength="60"
type="password"
>Senha Atual</s-input>
<s-button class="disNone mgnTB10" id="updateUserInfoUserPanelBTN">Atualizar Informações</s-button>
</div>
</div>
</s-popup>
最后,需要相当长的时间才能完全迁移,并且当我切换到 Web 组件时,我注意到页面加载变得非常慢。 Google Lighthouse 性能得分从 90 下降到 40...
即使我尝试了延迟加载、延迟渲染,我也将 JS 分割成包并按要求交付它们,但我不认为这是使用渐进式 Web 应用程序的好方法...
现在我不知道这是否值得...我觉得我浪费了太多时间“原地打转”,结果页面速度慢了很多...
我应该继续使用 Web 组件还是继续使用旧的快速方法来开发 UI?
我找到了解决方案!通过做更多的研究,我现在明白,通过像这样使用innerHTML来嵌套Web组件并不是一个好主意:
老
<s-popup>
class Popup extends HTMLElement {
constructor(){
super();
// Init ShadowRoot
const shadowRoot = this.attachShadow({ mode: 'open' });
// Add class to all popups
this.classList.add('popup');
this.classList.add('disNone');
// Get tag attributes
const id = this.getAttribute('id')??'';
const placeholder = this.getAttribute('placeholder')??'';
const content = this.innerHTML??'';
// Design HTML component
shadowRoot.innerHTML = `
<style>
@import url('./assets/style.min.css');
@import url('./assets/~bootstrap-icons/font/bootstrap-icons.css');
:host{ display: flex; }
</style>
<div class="popupWrap1">
<div class="popupWrap2">
<div class="popupWrap3 popupCloser">
<div class="popupWrap4 popupCloser">
<div class="popupDialog popupFilm" onclick="event.stopPropagation();">
<div class="popupTitle flexCenter">
${placeholder}
<i class="bi bi-square-fill bgSquareClose"></i>
<i class="bi bi-x-square-fill popupCloseIcon" onclick="closePopup(${id});"></i>
<div class="popupReturn flexCenter"></div>
<div class="titleBottomBorder"></div>
</div>
${content}
</div>
</div>
</div>
</div>
</div>`;
// Popup closer
this.shadowRoot.querySelector('.popupCloser').onclick = e =>{ closePopup(this); }
}
hideNavBars(){
this.shadowRoot.querySelector('.popupWrap1').style.marginTop = '0px';
this.shadowRoot.querySelector('.popupWrap1').style.marginBottom = '0px';
}
showNavBars(){
this.shadowRoot.querySelector('.popupWrap1').style.marginTop = nbHeaderHeight + 'px';
this.shadowRoot.querySelector('.popupWrap1').style.marginBottom = nbFooterHeight + 'px';
}
}
customElements.define('s-popup', Popup);
但只需使用
<slot>
标签而不是innerHTML 就可以了,现在页面加载速度非常快!
新
<s-popup>
class Popup extends HTMLElement {
constructor(){
super();
// Init ShadowRoot
const shadowRoot = this.attachShadow({ mode: 'open' });
// Add class to all popups
this.classList.add('popup');
this.classList.add('disNone');
// Get tag attributes
const id = this.getAttribute('id')??'';
const placeholder = this.getAttribute('placeholder')??'';
// Design HTML component
shadowRoot.innerHTML = `
<style>
@import url('./assets/style.min.css');
@import url('./assets/~bootstrap-icons/font/bootstrap-icons.css');
:host{ display: flex; }
</style>
<div class="popupWrap1">
<div class="popupWrap2">
<div class="popupWrap3 popupCloser">
<div class="popupWrap4 popupCloser">
<div class="popupDialog popupFilm" onclick="event.stopPropagation();">
<div class="popupTitle flexCenter">
${placeholder}
<i class="bi bi-square-fill bgSquareClose"></i>
<i class="bi bi-x-square-fill popupCloseIcon" onclick="closePopup(${id});"></i>
<div class="popupReturn flexCenter"></div>
<div class="titleBottomBorder"></div>
</div>
<slot></slot>
</div>
</div>
</div>
</div>
</div>`;
// Popup closer
this.shadowRoot.querySelector('.popupCloser').onclick = e =>{ closePopup(this); }
}
hideNavBars(){
this.shadowRoot.querySelector('.popupWrap1').style.marginTop = '0px';
this.shadowRoot.querySelector('.popupWrap1').style.marginBottom = '0px';
}
showNavBars(){
this.shadowRoot.querySelector('.popupWrap1').style.marginTop = nbHeaderHeight + 'px';
this.shadowRoot.querySelector('.popupWrap1').style.marginBottom = nbFooterHeight + 'px';
}
}
customElements.define('s-popup', Popup);
就这么简单!
原因是当使用innerHTML嵌套Web组件时,每次我们渲染每个内部嵌套组件时,整个DOM都需要更新,因为innerHTML直接操作DOM。但通过使用
<slot>
来代替,浏览器会创建一个 Virtual DOM,然后使用某种快速内存分配来渲染其中的页面,最后,它只对 DOM 进行简单的单次更新。现在它就像魅力一样发挥作用!