Компонентизация — важное направление фронтенд-разработки, с одной стороны, повышающая эффективность разработки, а с другой — снижающая затраты на сопровождение. Основной поток Vue.js, React и его расширения Ant Design, uniapp, Taro и т. д. — все это компонентные фреймворки. Web Components— это общий термин для набора собственных веб-API, которые позволяют нам создавать повторно используемые пользовательские компоненты и использовать их в качестве собственных тегов HTML в наших веб-приложениях. В настоящее время многие интерфейсные фреймворки/библиотеки поддерживаютWeb Components.
Эта статья познакомит вас сWeb ComponentsCore API и реализовать библиотеку бизнес-компонентов на основе разработки API веб-компонентов от 0 до 1.
окончательный эффект:Пропингуйте 8787.com/exe-comp один…Адрес склада:GitHub.com/Ping An8787/…
1. Просмотрите веб-компоненты
В истории фронтенд-разработки, от начала повторения бизнеса и повсеместного копирования одного и того же кода до появления веб-компонентов, мы использовали пользовательские компоненты с нативными HTML-тегами для повторного использования кода компонентов для повышения эффективности разработки. Компоненты, созданные с помощью веб-компонентов, можно использовать практически в любой интерфейсной среде.
1. Обзор основного API
Веб-компоненты состоят из 3 основных API:
- Пользовательские элементы: используется для определенияпользовательский элемента такжеповедение, который предоставляет метку компонента для внешнего мира;
- Тень ДОМ: используется для инкапсуляции внутренней структуры компонента во избежание конфликтов с внешней средой;
-
HTML-шаблоны:включать
<template>а также<slot>Элементы, поэтому мы можем определить HTML-шаблоны различных компонентов, а затем мультиплексировать в другом месте, студенты использовали каркас Vue / React и т. Д. Должен быть очень знакомым.
Кроме того, есть HTML-импорты, но они в настоящее время устарели, поэтому подробно не представлены, а их роль заключается в управлении загрузкой зависимостей компонентов.
2. Пример начала работы
Давайте быстро рассмотрим следующий простой примерКак создать простые веб-компоненты.
- использовать компоненты
<!DOCTYPE html>
<html lang="en">
<head>
<script src="./index.js" defer></script>
</head>
<body>
<h1>custom-element-start</h1>
<custom-element-start></custom-element-start>
</body>
</html>
- определить компоненты
/**
* 使用 CustomElementRegistry.define() 方法用来注册一个 custom element
* 参数如下:
* - 元素名称,符合 DOMString 规范,名称不能是单个单词,且必须用短横线隔开
* - 元素行为,必须是一个类
* - 继承元素,可选配置,一个包含 extends 属性的配置对象,指定创建的元素继承自哪个内置元素,可以继承任何内置元素。
*/
class CustomElementStart extends HTMLElement {
constructor(){
super();
this.render();
}
render(){
const shadow = this.attachShadow({mode: 'open'});
const text = document.createElement("span");
text.textContent = 'Hi Custom Element!';
text.style = 'color: red';
shadow.append(text);
}
}
customElements.define('custom-element-start', CustomElementStart)
Приведенный выше код в основном делает 3 вещи:
- Реализовать класс компонента
путем реализацииCustomElementStartклассы для определения компонентов.
- определить компоненты
Принимая метку компонента и класс компонента в качестве параметров, передайтеcustomElements.defineМетоды определяют компоненты.
- использовать компоненты
После импорта компонента используйте пользовательский компонент напрямую, как обычные HTML-теги.<custom-element-start></custom-element-start>.
Затем доступ к браузеруindex.htmlВы можете увидеть следующее:
3. Введение в совместимость
существуетMDN | Web ComponentsСовместимость описана в главах:
- Firefox (версия 63), Chrome и Opera по умолчанию поддерживают веб-компоненты.
- Safari поддерживает множество функций веб-компонентов, но меньше, чем вышеупомянутые браузеры.
- Edge работает над реализацией.
Для совместимости вы можете увидеть следующий рисунок:Источник изображения:www.webcomponents.org/
На этом веб-сайте есть много отличных проектов о веб-компонентах для изучения.
4. Резюме
В этом разделе в основном используется простой пример для краткого обзора основ, а подробности вы можете прочитать в документации:
2. Анализ и проектирование библиотеки компонентов EXE-Components
1. Введение
Предположим, нам нужно реализовать библиотеку компонентов EXE-Components, компоненты библиотеки компонентов разделены на 2 категории:
- тип компонентов
кОбщие простые компонентыглавным образом, какexe-avatarкомпонент аватара,exe-buttonкомпоненты кнопок и т.д.;
- тип модулей
ксложные составные компонентыглавным образом, какexe-user-avatarКомпонент аватара пользователя (включая информацию о пользователе),exe-attachement-listКомпонент списка вложений и многое другое.
Подробности можно увидеть ниже:
Далее мы спроектируем и разработаем библиотеку компонентов EXE-Components на основе рисунка выше.
2. Дизайн библиотеки компонентов
При проектировании библиотеки компонентов необходимо учитывать следующие моменты:
- Именование компонентов, наименование параметров и другие спецификации для облегчения последующего обслуживания компонентов;
- Определение параметров компонента;
- Изоляция стиля компонента;
Конечно, это самые основные моменты, которые необходимо учитывать.С учетом сложности реального бизнеса необходимо учитывать больше, например: связанные с проектированием, развязку компонентов, тему компонентов и так далее.
В ответ на три пункта, упомянутых выше, вот несколько соглашений об именах:
- Имя компонента начинается с
exe-功能名称названный какexe-avatarПредставляет компонент аватара; - Имена параметров свойства начинаются с
e-参数名称названный какe-srcвыражатьsrcАтрибут адреса; - Имена параметров событий начинаются с
on-事件类型названный какon-clickПредставляет событие щелчка;
3. Дизайн компонентов библиотеки компонентов
Здесь мы в основном проектируемexe-avatar,exe-buttonа такжеexe-user-avatarТри компонента, первые два являются простыми компонентами, последний представляет собой сложный компонент, а первые два компонента используются внутри для комбинирования. Здесь сначала определите свойства, поддерживаемые этими тремя компонентами:
Здесь именование атрибута кажется более сложным, вы можете назвать его в соответствии со своими и командными привычками.
Таким образом, наше мышление становится намного яснее, и мы можем реализовать соответствующие компоненты.
Три, подготовка библиотеки компонентов EXE-компонентов
Примеры в этой статье в конечном итоге выполнят реализацию компонентовиспользовать в сочетании, реализовать следующее "список пользователей"Эффект:Адрес опыта:Пропингуйте 8787.com/exe-comp один…
1. Единые спецификации разработки
Во-первых, давайте унифицируем спецификации разработки, в том числе:
- Спецификация каталога
- Определение спецификаций компонентов
- Шаблон разработки компонентов
шаблон разработки компонентовindex.jsфайл входа компонентаа такжеtemplate.js Файл HTML-шаблона компонента:
// index.js 模版
const defaultConfig = {
// 组件默认配置
}
const Selector = "exe-avatar"; // 组件标签名
export default class EXEAvatar extends HTMLElement {
shadowRoot = null;
config = defaultConfig;
constructor(){
super();
this.render(); // 统一处理组件初始化逻辑
}
render() {
this.shadowRoot = this.attachShadow({mode: 'closed'});
this.shadowRoot.innerHTML = renderTemplate(this.config);
}
}
// 定义组件
if (!customElements.get(Selector)) {
customElements.define(Selector, EXEAvatar)
}
// template.js 模版
export default config => {
// 统一读取配置
const { avatarWidth, avatarRadius, avatarSrc } = config;
return `
<style>
/* CSS 内容 */
</style>
<div class="exe-avatar">
/* HTML 内容 */
</div>
`
}
2. Строительство среды разработки и инженерная обработка
Чтобы облегчить использование библиотеки компонентов EXE-Components и приблизиться к использованию фактической библиотеки компонентов, нам необходимо упаковать библиотеку компонентов в файл js типа UMD. Здесь мы используемrollupСоберите и, наконец, упакуйте вexe-components.jsфайл, который используется следующим образом:
<script src="./exe-components.js"></script>
Далее черезnpm init -yгенерироватьpackage.jsonфайл, затем установите накопительный пакет глобально иhttp-server(Используется для запуска локального сервера для упрощения отладки):
npm init -y
npm install --global rollup http-server
затем вpackage.jsonизscriptдобавить под"dev"а также"build"сценарий:
{
// ...
"scripts": {
"dev": "http-server -c-1 -p 1400",
"build": "rollup index.js --file exe-components.js --format iife"
},
}
в:
-
"dev"Команда: Запустите статический сервер через http-сервер для использования в качестве среды разработки. Добавить к-c-1Параметр используется для отключения кеша, чтобы не обновлять страницу и будет кеш.Подробнее см.документация http-сервера; -
"build"Команда: использовать index.js в качестве входного файла для накопительной упаковки, выводexe-components.jsфайл и имеет тип iife.
Таким образом, инженерная конфигурация простой локальной разработки и построения библиотеки компонентов завершена, после чего можно выполнять разработку.
В-четвертых, разработка библиотеки компонентов EXE-компонентов.
1. Конфигурация файла записи библиотеки компонентов
Переднийpackage.jsonнастроено в файле"build"команда, будет использовать корневой каталогindex.jsВ качестве входного файла, а также для облегчения введения компонентов, общих базовых компонентов и модулей, общих сложных компонентов, мы создаем 3index.js, структура каталогов после создания выглядит следующим образом:Содержимое трех входных файлов следующее:
// EXE-Components/index.js
import './components/index.js';
import './modules/index.js';
// EXE-Components/components/index.js
import './exe-avatar/index.js';
import './exe-button/index.js';
// EXE-Components/modules/index.js
import './exe-attachment-list/index.js.js';
import './exe-comment-footer/index.js.js';
import './exe-post-list/index.js.js';
import './exe-user-avatar/index.js';
2. Разработайте файл index.js компонента exe-аватар.
Из предыдущего анализа мы можем знать, чтоexe-avatarКомпоненты должны поддерживать параметры:
- e-avatar-src: адрес изображения аватара, например: ./testAssets/images/avatar-1.png
- e-avatar-width: Ширина аватара, по умолчанию такая же, как высота, например: 52px
- e-button-radius: закругленные углы аватара, например: 22px, по умолчанию: 50%
- on-avatar-click: событие щелчка аватара, по умолчанию нет
Затем по предыдущему шаблону разработайте входной файлindex.js:
// EXE-Components/components/exe-avatar/index.js
import renderTemplate from './template.js';
import { Shared, Utils } from '../../utils/index.js';
const { getAttributes } = Shared;
const { isStr, runFun } = Utils;
const defaultConfig = {
avatarWidth: "40px",
avatarRadius: "50%",
avatarSrc: "./assets/images/default_avatar.png",
onAvatarClick: null,
}
const Selector = "exe-avatar";
export default class EXEAvatar extends HTMLElement {
shadowRoot = null;
config = defaultConfig;
constructor(){
super();
this.render();
}
render() {
this.shadowRoot = this.attachShadow({mode: 'closed'});
this.shadowRoot.innerHTML = renderTemplate(this.config);// 生成 HTML 模版内容
}
// 生命周期:当 custom element首次被插入文档DOM时,被调用。
connectedCallback() {
this.updateStyle();
this.initEventListen();
}
updateStyle() {
this.config = {...defaultConfig, ...getAttributes(this)};
this.shadowRoot.innerHTML = renderTemplate(this.config); // 生成 HTML 模版内容
}
initEventListen() {
const { onAvatarClick } = this.config;
if(isStr(onAvatarClick)){ // 判断是否为字符串
this.addEventListener('click', e => runFun(e, onAvatarClick));
}
}
}
if (!customElements.get(Selector)) {
customElements.define(Selector, EXEAvatar)
}
Некоторые из этих методов являются извлекаемыми общедоступными методами, а их функции кратко представлены.Подробности см. в исходном коде:
-
renderTemplateметод
Из методов, предоставляемых template.js, передайте конфигурацию конфигурации для создания HTML-шаблона.
-
getAttributesметод
Передайте элемент HTMLElement и верните все пары ключ-значение атрибута в элементе, который будетe-а такжеon-Атрибуты в начале перерабатываются в обычные атрибуты и атрибуты событий соответственно. Примеры следующие:
// input
<exe-avatar
e-avatar-src="./testAssets/images/avatar-1.png"
e-avatar-width="52px"
e-avatar-radius="22px"
on-avatar-click="avatarClick()"
></exe-avatar>
// output
{
avatarSrc: "./testAssets/images/avatar-1.png",
avatarWidth: "52px",
avatarRadius: "22px",
avatarClick: "avatarClick()"
}
-
runFunметод
Поскольку метод, передаваемый через атрибут, является строкой, он инкапсулируется и передается вeventИ имя события в качестве параметра, вызовите этот метод, пример такой же, как и в предыдущем шаге, он выполнитavatarClick()метод.
Кроме того, жизненный цикл веб-компонентов можно подробно посмотреть в документации:Использование функций обратного вызова жизненного цикла.
3. Разработайте файл template.js компонента exe-аватар
Этот файл предоставляет метод, который возвращает HTML-шаблон компонента:
// EXE-Components/components/exe-avatar/template.js
export default config => {
const { avatarWidth, avatarRadius, avatarSrc } = config;
return `
<style>
.exe-avatar {
width: ${avatarWidth};
height: ${avatarWidth};
display: inline-block;
cursor: pointer;
}
.exe-avatar .img {
width: 100%;
height: 100%;
border-radius: ${avatarRadius};
border: 1px solid #efe7e7;
}
</style>
<div class="exe-avatar">
<img class="img" src="${avatarSrc}" />
</div>
`
}
Окончательный эффект выглядит следующим образом:
После разработки первого компонента мы можем кратко суммировать шаги по созданию и использованию компонента:
4. Разработать компонент exe-кнопки
следовать впередexe-avatarИдеи разработки компонентов, которые можно быстро реализоватьexe-buttonкомпоненты.
Необходимо поддерживать следующие параметры:
- e-button-radius: закругленные углы кнопки, например: 8px
- e-button-type: тип кнопки, например: по умолчанию, основная, текстовая, пунктирная
- e-button-text: текст кнопки, по умолчанию: открыть
- on-button-click: событие нажатия кнопки, по умолчанию нет
// EXE-Components/components/exe-button/index.js
import renderTemplate from './template.js';
import { Shared, Utils } from '../../utils/index.js';
const { getAttributes } = Shared;
const { isStr, runFun } = Utils;
const defaultConfig = {
buttonRadius: "6px",
buttonPrimary: "default",
buttonText: "打开",
disableButton: false,
onButtonClick: null,
}
const Selector = "exe-button";
export default class EXEButton extends HTMLElement {
// 指定观察到的属性变化,attributeChangedCallback 会起作用
static get observedAttributes() {
return ['e-button-type','e-button-text', 'buttonType', 'buttonText']
}
shadowRoot = null;
config = defaultConfig;
constructor(){
super();
this.render();
}
render() {
this.shadowRoot = this.attachShadow({mode: 'closed'});
}
connectedCallback() {
this.updateStyle();
this.initEventListen();
}
attributeChangedCallback (name, oldValue, newValue) {
// console.log('属性变化', name)
}
updateStyle() {
this.config = {...defaultConfig, ...getAttributes(this)};
this.shadowRoot.innerHTML = renderTemplate(this.config);
}
initEventListen() {
const { onButtonClick } = this.config;
if(isStr(onButtonClick)){
const canClick = !this.disabled && !this.loading
this.addEventListener('click', e => canClick && runFun(e, onButtonClick));
}
}
get disabled () {
return this.getAttribute('disabled') !== null;
}
get type () {
return this.getAttribute('type') !== null;
}
get loading () {
return this.getAttribute('loading') !== null;
}
}
if (!customElements.get(Selector)) {
customElements.define(Selector, EXEButton)
}
Шаблон определяется следующим образом:
// EXE-Components/components/exe-button/tempalte.js
// 按钮边框类型
const borderStyle = { solid: 'solid', dashed: 'dashed' };
// 按钮类型
const buttonTypeMap = {
default: { textColor: '#222', bgColor: '#FFF', borderColor: '#222'},
primary: { textColor: '#FFF', bgColor: '#5FCE79', borderColor: '#5FCE79'},
text: { textColor: '#222', bgColor: '#FFF', borderColor: '#FFF'},
}
export default config => {
const { buttonRadius, buttonText, buttonType } = config;
const borderStyleCSS = buttonType
&& borderStyle[buttonType]
? borderStyle[buttonType]
: borderStyle['solid'];
const backgroundCSS = buttonType
&& buttonTypeMap[buttonType]
? buttonTypeMap[buttonType]
: buttonTypeMap['default'];
return `
<style>
.exe-button {
border: 1px ${borderStyleCSS} ${backgroundCSS.borderColor};
color: ${backgroundCSS.textColor};
background-color: ${backgroundCSS.bgColor};
font-size: 12px;
text-align: center;
padding: 4px 10px;
border-radius: ${buttonRadius};
cursor: pointer;
display: inline-block;
height: 28px;
}
:host([disabled]) .exe-button{
cursor: not-allowed;
pointer-events: all;
border: 1px solid #D6D6D6;
color: #ABABAB;
background-color: #EEE;
}
:host([loading]) .exe-button{
cursor: not-allowed;
pointer-events: all;
border: 1px solid #D6D6D6;
color: #ABABAB;
background-color: #F9F9F9;
}
</style>
<button class="exe-button">${buttonText}</button>
`
}
Окончательный эффект выглядит следующим образом:
5. Разработайте компонент exe-user-avatar
Этот компонент является переднимexe-avatarкомпоненты иexe-buttonкомпоненты объединены, нужно не только поддерживатьсобытие щелчка, тоже нужна поддержкаслот слот функция.
Поскольку это комбинация, ее относительно просто разработать ~ сначала взгляните на файл ввода:
// EXE-Components/modules/exe-user-avatar/index.js
import renderTemplate from './template.js';
import { Shared, Utils } from '../../utils/index.js';
const { getAttributes } = Shared;
const { isStr, runFun } = Utils;
const defaultConfig = {
userName: "",
subName: "",
disableButton: false,
onAvatarClick: null,
onButtonClick: null,
}
export default class EXEUserAvatar extends HTMLElement {
shadowRoot = null;
config = defaultConfig;
constructor() {
super();
this.render();
}
render() {
this.shadowRoot = this.attachShadow({mode: 'open'});
}
connectedCallback() {
this.updateStyle();
this.initEventListen();
}
initEventListen() {
const { onAvatarClick } = this.config;
if(isStr(onAvatarClick)){
this.addEventListener('click', e => runFun(e, onAvatarClick));
}
}
updateStyle() {
this.config = {...defaultConfig, ...getAttributes(this)};
this.shadowRoot.innerHTML = renderTemplate(this.config);
}
}
if (!customElements.get('exe-user-avatar')) {
customElements.define('exe-user-avatar', EXEUserAvatar)
}
Основное содержимое находится в template.js:
// EXE-Components/modules/exe-user-avatar/template.js
import { Shared } from '../../utils/index.js';
const { renderAttrStr } = Shared;
export default config => {
const {
userName, avatarWidth, avatarRadius, buttonRadius,
avatarSrc, buttonType = 'primary', subName, buttonText, disableButton,
onAvatarClick, onButtonClick
} = config;
return `
<style>
:host{
color: "green";
font-size: "30px";
}
.exe-user-avatar {
display: flex;
margin: 4px 0;
}
.exe-user-avatar-text {
font-size: 14px;
flex: 1;
}
.exe-user-avatar-text .text {
color: #666;
}
.exe-user-avatar-text .text span {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
}
exe-avatar {
margin-right: 12px;
width: ${avatarWidth};
}
exe-button {
width: 60px;
display: flex;
justify-content: end;
}
</style>
<div class="exe-user-avatar">
<exe-avatar
${renderAttrStr({
'e-avatar-width': avatarWidth,
'e-avatar-radius': avatarRadius,
'e-avatar-src': avatarSrc,
})}
></exe-avatar>
<div class="exe-user-avatar-text">
<div class="name">
<span class="name-text">${userName}</span>
<span class="user-attach">
<slot name="name-slot"></slot>
</span>
</div>
<div class="text">
<span class="name">${subName}<slot name="sub-name-slot"></slot></span>
</div>
</div>
${
!disableButton &&
`<exe-button
${renderAttrStr({
'e-button-radius' : buttonRadius,
'e-button-type' : buttonType,
'e-button-text' : buttonText,
'on-avatar-click' : onAvatarClick,
'on-button-click' : onButtonClick,
})}
></exe-button>`
}
</div>
`
}
вrenderAttrStrМетод принимает объект свойства и возвращает его строку пары ключ-значение:
// input
{
'e-avatar-width': 100,
'e-avatar-radius': 50,
'e-avatar-src': './testAssets/images/avatar-1.png',
}
// output
"e-avatar-width='100' e-avatar-radius='50' e-avatar-src='./testAssets/images/avatar-1.png' "
Окончательный эффект выглядит следующим образом:
6. Внедрите бизнес со списком пользователей
Далее, давайте посмотрим на влияние наших компонентов на реальный бизнес:
На самом деле реализация тоже очень проста, согласно приведенным данным, компоненты могут быть переработаны и использованы, предполагая следующие пользовательские данные:
const users = [
{"name":"前端早早聊","desc":"帮 5000 个前端先跑 @ 前端早早聊","level":6,"avatar":"qdzzl.jpg","home":"https://juejin.cn/user/712139234347565"}
{"name":"来自拉夫德鲁的码农","desc":"谁都不救我,谁都救不了我,就像我救不了任何人一样","level":2,"avatar":"lzlfdldmn.jpg","home":"https://juejin.cn/user/994371074524862"}
{"name":"黑色的枫","desc":"永远怀着一颗学徒的心。。。","level":3,"avatar":"hsdf.jpg","home":"https://juejin.cn/user/2365804756348103"}
{"name":"captain_p","desc":"目的地很美好,路上的风景也很好。今天增长见识了吗","level":2,"avatar":"cap.jpg","home":"https://juejin.cn/user/2532902235026439"}
{"name":"CUGGZ","desc":"文章联系微信授权转载。微信:CUG-GZ,添加好友一起学习~","level":5,"avatar":"cuggz.jpg","home":"https://juejin.cn/user/3544481220801815"}
{"name":"政采云前端团队","desc":"政采云前端 ZooTeam 团队,不掺水的原创。 团队站点:https://zoo.team","level":6,"avatar":"zcy.jpg","home":"https://juejin.cn/user/3456520257288974"}
]
Мы можем объединить фрагменты HTML с помощью простого цикла for и добавить их к элементу на странице:
// 测试生成用户列表模版
const usersTemp = () => {
let temp = '', code = '';
users.forEach(item => {
const {name, desc, level, avatar, home} = item;
temp +=
`
<exe-user-avatar
e-user-name="${name}"
e-sub-name="${desc}"
e-avatar-src="./testAssets/images/users/${avatar}"
e-avatar-width="36px"
e-button-type="primary"
e-button-text="关注"
on-avatar-click="toUserHome('${home}')"
on-button-click="toUserFollow('${name}')"
>
${
level >= 0 && `<span slot="name-slot">
<span class="medal-item">(Lv${level})</span>
</span>`}
</exe-user-avatar>
`
})
return temp;
}
document.querySelector('#app').innerHTML = usersTemp;
На данный момент мы поняли, что такое список пользователей.Конечно, реальный бизнес может быть более сложным и нуждается в оптимизации.
V. Резюме
В этой статье сначала кратко рассматривается основной API веб-компонентов, затем анализируются и проектируются требования к библиотеке компонентов, а затем создается и разрабатывается среда. Контента много, и он может не охватывать все пункты. посмотрите на исходный код моего склада.В чем проблема?Добро пожаловать, чтобы обсудить со мной. Несколько основных целей написания этой статьи:
- Когда мы получаем новое задание, нам нужно начинать с анализа и проектирования, а потом уже к разработке, а не начинать разработку вслепую;
- Давайте посмотрим, как разработать простую библиотеку бизнес-компонентов с помощью веб-компонентов;
- Испытайте недостатки разработки библиотеки компонентов с помощью веб-компонентов (то есть слишком много вещей, которые нужно написать).
После прочтения этой статьи вы думаете, что разрабатывать библиотеки компонентов с помощью веб-компонентов сложно? Слишком много, чтобы написать. Это не имеет значения, в следующей статье я возьму вас с собой, чтобы использовать его вместеStencilФреймворк развивает стандартную библиотеку компонентов Web Components, ведь вся ionic уже используюStencilРефакторинг, общая тенденция веб-компонентов~!