Создайте веб-приложение с помощью современного JavaScript и веб-компонентов

Создайте веб-приложение с помощью современного JavaScript и веб-компонентов Программирование и разработка

JavaScript в браузере эволюционировал. Разработчики, которые хотят воспользоваться новейшими функциями, могут отказаться от фреймворка с меньшими хлопотами. Параметры, обычно зарезервированные для интерфейсных фреймворков, такие как компонентный подход, теперь возможны в простом старом JavaScript.

В этом дубле я продемонстрирую все новейшие функции JavaScript, используя пользовательский интерфейс, который содержит данные об авторе с сеткой и фильтром поиска. Чтобы не усложнять, как только техника будет представлена, я перейду к следующей технике, чтобы не вдаваться в подробности. По этой причине в пользовательском интерфейсе будет опция «Добавить» и выпадающий фильтр поиска. Модель автора будет иметь три поля: имя, адрес электронной почты и необязательную тему. Валидация формы будет включена в основном для того, чтобы продемонстрировать эту безфреймворковую технику, но не до конца.

Когда-то смелый язык вырос с множеством современных функций, таких как прокси, импорт / экспорт, дополнительный оператор цепочки и веб-компоненты. Это идеально подходит для Jamstack, потому что приложение обрабатывает на клиенте с помощью HTML и обычного JavaScript.

Я оставлю API, чтобы сосредоточиться на приложении, но я укажу, где эта интеграция может происходить внутри приложения.

Getting Started

Приложение представляет собой типичное приложение JavaScript с двумя зависимостями: http-сервер и Bootstrap. Код будет работать только в браузере, поэтому нет другой серверной части, кроме одной для размещения статических ресурсов. Код размещен на GitHub, и вы можете с ним поиграть.

Предполагая, что на вашем компьютере установлена последняя версия Node LTS :

mkdir framework-less-web-components
cd framework-less-web-components
npm init

В результате должен получиться единственный package.jsonфайл, в который будут помещены зависимости.

Чтобы установить две зависимости:

npm i http-server bootstrap@next --save-exact
  • http-server : HTTP-сервер для размещения статических ресурсов в Jamstack.
  • Bootstrap : элегантный и мощный набор стилей CSS для облегчения веб-разработки.

Если вы чувствуете, что http-serverэто не зависимость, а требование для запуска этого приложения, есть возможность установить его глобально через npm i -g http-server. В любом случае эта зависимость не доставляется клиенту, а обслуживает только статические активы.

Откройте package.jsonфайл и установите точку входа через «start»: «http-server«под scripts. Идите вперед и запустите приложение через npm start, которое станет http://localhost:8080/доступным для браузера. Любой index.htmlфайл, помещенный в корневую папку, автоматически размещается на HTTP-сервере. Все, что вам нужно сделать, это обновить страницу, чтобы получить самые свежие данные.

Структура папок выглядит так:

┳
┣━┓ components
┃ ┣━━ App.js
┃ ┣━━ AuthorForm.js
┃ ┣━━ AuthorGrid.js
┃ ┗━━ ObservableElement.js
┣━┓ model
┃ ┣━━ actions.js
┃ ┗━━ observable.js
┣━━ index.html
┣━━ index.js
┗━━ package.json

Вот для чего предназначена каждая папка:

  • components: Веб-компоненты HTML с App.jsэлементами и настраиваемыми элементами, которые наследуются отObservableElement.js
  • model: состояние и мутации приложения, которые отслеживают изменения состояния пользовательского интерфейса
  • index.html: основной файл статических ресурсов, который можно разместить где угодно

Чтобы создать папки и файлы в каждой папке, выполните следующее:

mkdir components model
touch components/App.js components/AuthorForm.js components/AuthorGrid.js components/ObservableElement.js model/actions.js model/observable.js index.html index.js

Интегрировать веб-компоненты

Вкратце, веб-компоненты — это настраиваемые HTML-элементы. Они определяют настраиваемый элемент, который может быть помещен в разметку, и объявляют метод обратного вызова, который отображает компонент.

Вот краткое изложение настраиваемого веб-компонента:

class HelloWorldComponent extends HTMLElement {
  connectedCallback() { // callback method
    this.innerHTML = 'Hello, World!'
  }
}

// Define the custom element
window.customElements.define('hello-world', HelloWorldComponent)

// The markup can use this custom web component via:
// <hello-world></hello-world>

Если вы чувствуете, что вам нужно более мягкое введение в веб-компоненты, ознакомьтесь со статьей MDN. Сначала они могут показаться волшебными, но хорошее понимание метода обратного вызова делает это совершенно очевидным.

На главной index.htmlстатической странице объявляются веб-компоненты HTML. Я буду использовать Bootstrap для стилизации HTML-элементов и добавления index.jsресурса, который станет основной точкой входа приложения и шлюзом в JavaScript.

Откройте index.htmlфайл и вставьте его на место:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link href="node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
  <title>Framework-less Components</title>
</head>
<body>
<template id="html-app">
  <div class="container">
    <h1>Authors</h1>
    <author-form></author-form>
    <author-grid></author-grid>
    <footer class="fixed-bottom small">
      <p class="text-center mb-0">
        Hit Enter to add an author entry
      </p>
      <p class="text-center small">
        Created with ❤ By C R
      </p>
    </footer>
  </div>
</template>
<template id="author-form">
  <form>
    <div class="row mt-4">
      <div class="col">
        <input type="text" class="form-control" placeholder="Name" aria-label="Name">
      </div>
      <div class="col">
        <input type="email" class="form-control" placeholder="Email" aria-label="Email">
      </div>
      <div class="col">
        <select class="form-select" aria-label="Topic">
          <option>Topic</option>
          <option>JavaScript</option>
          <option>HTMLElement</option>
          <option>ES7+</option>
        </select>
      </div>
      <div class="col">
        <select class="form-select search" aria-label="Search">
          <option>Search by</option>
          <option>All</option>
          <option>JavaScript</option>
          <option>HTMLElement</option>
          <option>ES7+</option>
        </select>
      </div>
    </div>
  </form>
</template>
<template id="author-grid">
  <table class="table mt-4">
    <thead>
      <tr>
        <th>Name</th>
        <th>Email</th>
        <th>Topic</th>
      </tr>
    </thead>
    <tbody>
    </tbody>
  </table>
</template>
<template id="author-row">
  <tr>
    <td></td>
    <td></td>
    <td></td>
  </tr>
</template>
<nav class="navbar navbar-expand-lg navbar-light bg-dark">
  <div class="container-fluid">
    <a class="navbar-brand text-light" href="/">
      Framework-less Components with Observables
    </a>
  </div>
</nav>
<html-app></html-app>
<script type="module" src="index.js"></script>
</body>
</html>

Обратите особое внимание на scriptтег с typeатрибутом, установленным на module. Это то, что разблокирует импорт / экспорт в обычном JavaScript в браузере. templateТег с idопределяет HTML — элементы, которые позволяют веб — компоненты. Я распался приложение на три основных компоненты: html-app, author-formи author-grid. Поскольку в JavaScript еще ничего не определено, приложение будет отображать панель навигации без каких-либо пользовательских тегов HTML.

Чтобы начать легко, поместите это в ObservableElement.js. Это родительский элемент для всех компонентов автора:

export default class ObservableElement extends HTMLElement {
}

Затем определите html-appкомпонент в App.js:

export default class App extends HTMLElement {
  connectedCallback() {
    this.template = document
      .getElementById('html-app')

    window.requestAnimationFrame(() => {
      const content = this.template
        .content
        .firstElementChild
        .cloneNode(true)

      this.appendChild(content)
    })
  }
}

Обратите внимание на использование export defaultдля объявления классов JavaScript. Эту возможность я включил с помощью moduleтипа, когда ссылался на основной файл сценария. Чтобы использовать веб-компоненты, унаследуйте метод класса HTMLElementи определите его connectedCallback. Об остальном позаботится браузер. Я использую requestAnimationFrameдля рендеринга основного шаблона перед следующей перерисовкой в ​​браузере.

Это распространенный метод, который вы увидите с веб-компонентами. Сначала возьмите шаблон по идентификатору элемента. Затем клонируйте шаблон через cloneNode. Наконец, appendChildновое contentв DOM. Если вы столкнетесь с какими-либо проблемами, когда веб-компоненты не отображаются, убедитесь, что клонированный контент сначала был добавлен в DOM.

Затем определите AuthorGrid.jsвеб-компонент. Этот будет следовать аналогичному шаблону и немного манипулировать DOM:

import ObservableElement from './ObservableElement.js'

export default class AuthorGrid extends ObservableElement {
  connectedCallback() {
    this.template = document
      .getElementById('author-grid')
    this.rowTemplate = document
      .getElementById('author-row')
    const content = this.template
      .content
      .firstElementChild
      .cloneNode(true)
    this.appendChild(content)

    this.table = this.querySelector('table')
    this.updateContent()
  }

  updateContent() {
    this.table.style.display =
      (this.authors?.length ?? 0) === 0
        ? 'none'
        : ''

    this.table
      .querySelectorAll('tbody tr')
      .forEach(r => r.remove())
  }
}

Я определил главный this.tableэлемент с помощью querySelector. Поскольку это класс, можно сохранить хорошую ссылку на целевой элемент, используя this. Этот updateContentметод в основном уничтожает основную таблицу, когда в сетке нет авторов. По желанию оператора цепочки ( ?.) и нулевой коалесцирующий заботится о настройке displayстиля нет.

Взгляните на importоператор, потому что он вводит зависимость с полным расширением в имени файла. Если вы привыкли к разработке Node, то в этом она отличается от реализации в браузере, которая соответствует стандарту, где для этого требуется расширение файла, например.js. Учитесь у меня и обязательно ставьте расширение файла при работе в браузере.

Далее, AuthorForm.jsкомпонент состоит из двух основных частей: рендеринга HTML и подключения событий элемента к форме.

Чтобы отобразить форму, откройте AuthorForm.js:

import ObservableElement from './ObservableElement.js'

export default class AuthorForm extends ObservableElement {
  connectedCallback() {
    this.template = document
      .getElementById('author-form')
    const content = this.template
      .content
      .firstElementChild
      .cloneNode(true)

    this.appendChild(content)

    this.form = this.querySelector('form')
    this.form.querySelector('input').focus()
  }

  resetForm(inputs) {
    inputs.forEach(i => {
      i.value = ''
      i.classList.remove('is-valid')
    })
    inputs[0].focus()
  }
}

Он focusнаправляет пользователя, чтобы начать вводить текст на первом элементе ввода, доступном в форме. Обязательно ставьте DOM селекторы послеappendChild, так как в противном случае этот метод не будет работать. resetFormНе используется прямо сейчас, но сбросит состояние формы, когда пользователь нажимает клавишу Enter.

Подключите события через addEventListenerдобавление этого кода внутри connectedCallbackметода. Это можно добавить в самый конец connectedCallbackметода:

this.form
  .addEventListener('keypress', e => {
    if (e.key === 'Enter') {
      const inputs = this.form.querySelectorAll('input')
      const select = this.form.querySelector('select')

      console.log('Pressed Enter: ' +
        inputs[0].value + '|' +
        inputs[1].value + '|' +
        (select.value === 'Topic' ? '' : select.value))

      this.resetForm(inputs)
    }
  })

this.form
  .addEventListener('change', e => {
    if (e.target.matches('select.search')
      && e.target.value !== 'Search by') {
      console.log('Filter by: ' + e.target.value)
    }
  })

Это типичные прослушиватели событий, которые прикрепляются к this.formэлементу в DOM. changeСобытие делегации использует событие для прослушивания всех событий изменения в форме, но предназначается только select.searchэлемент. Это эффективный способ делегировать одно событие как можно большему количеству целевых элементов в родительском элементе. Если это сделано, ввод чего-либо в форме и нажатие Enter сбрасывает форму обратно в нулевое состояние.

Чтобы эти веб-компоненты отображались на клиенте, откройте index.jsи вставьте это:

import AuthorForm from './components/AuthorForm.js'
import AuthorGrid from './components/AuthorGrid.js'
import App from './components/App.js'

window.customElements.define('author-form', AuthorForm)
window.customElements.define('author-grid', AuthorGrid)
window.customElements.define('html-app', App)

Не стесняйтесь обновлять страницу в браузере и поиграть с пользовательским интерфейсом. Откройте инструменты разработчика и просматривайте сообщения консоли, когда вы нажимаете и вводите форму. Нажатие Tabклавиши должно помочь вам перемещаться между элементами ввода в документе HTML.

Validate the Form

Поигравшись с формой, вы можете заметить, что она принимает произвольный ввод, когда требуются и имя, и адрес электронной почты, а тема является необязательной. Подход без фреймворка может представлять собой комбинацию проверки HTML и небольшого количества JavaScript. К счастью, Bootstrap несколько упрощает эту задачу, добавляя / удаляя имена классов CSS через classListвеб-API.

Внутри AuthorForm.jsкомпонента найдите console.logв Enterключевом обработчике событий, найдите журнал с «Pressed Enter» и поместите его прямо над ним:

if (!this.isValid(inputs)) return

Затем определите isValidметод класса в AuthorForm. Это может выходить за рамки resetFormметода:

isValid(inputs) {
  let isInvalid = false

  inputs.forEach(i => {
    if (i.value && i.checkValidity()) {
      i.classList.remove('is-invalid')
      i.classList.add('is-valid')
    } else {
      i.classList.remove('is-valid')
      i.classList.add('is-invalid')
      isInvalid = true
    }
  })

  return !isInvalid
}

В обычном JavaScript при вызове checkValidityиспользуется встроенный валидатор HTML, потому что я пометил элемент ввода с помощью type=»email«. Чтобы проверить наличие обязательных полей, простая проверка правдивости помогает с помощью i.value. classListВеб — API добавляет или удаляет имена классов CSS, поэтому моделирование Bootstrap может выполнять свою работу.

А теперь попробуйте еще раз. Попытка ввести недопустимые данные теперь помечается, а действительные данные сбрасывают форму.

Наблюдаемые

Пришло время для мяса (или картошки для моих друзей-вегетарианцев) этого подхода, потому что веб-компоненты и обработчики событий могут только увести меня. Чтобы сделать это приложение управляемым состоянием, мне понадобится способ отслеживать изменения состояния пользовательского интерфейса. Оказывается, наблюдаемые объекты идеально подходят для этого, потому что они могут запускать обновления пользовательского интерфейса при изменении состояния. Думайте о наблюдаемых как о модели sub / pub, где подписчики отслеживают изменения, а издатель запускает те изменения, которые произошли в состоянии пользовательского интерфейса. Это упрощает объем кода push и pull, необходимый для создания сложных и захватывающих пользовательских интерфейсов без какой-либо инфраструктуры.

Откройте obserable.jsфайл modelи вставьте его:

const cloneDeep = x => JSON.parse(JSON.stringify(x))
const freeze = state => Object.freeze(cloneDeep(state))

export default initialState => {
  let listeners = []

  const proxy = new Proxy(cloneDeep(initialState), {
    set: (target, name, value) => {
      target[name] = value
      listeners.forEach(l => l(freeze(proxy)))
      return true
    }
  })

  proxy.addChangeListener = cb => {
    listeners.push(cb)
    cb(freeze(proxy))
    return () =>
      listeners = listeners.filter(el => el !== cb)
  }

  return proxy
}

Поначалу это может показаться пугающим, но он делает две вещи: захват установщика для перехвата мутаций и добавление слушателей. В ES6 + Proxyкласс включает прокси, который обтекает initialStateобъект. Это может перехватывать базовые операции, подобные этому setметоду, который выполняется при изменении объекта. Возврат trueв сеттере позволяет внутреннему механизму JavaScript узнать, что мутация прошла успешно. В Proxyустанавливает объект обработчика, где такие как ловушки setполучить определены. Поскольку меня интересуют только мутации объекта состояния, у setнего есть ловушка. Все остальные функции, такие как чтение, перенаправляются непосредственно в объект исходного состояния.

Слушатели хранят список подписанных обратных вызовов, которые хотят получать уведомления о мутациях. Обратный вызов выполняется один раз после добавления слушателя и возвращает прослушивающий обратный вызов для использования в будущем.

Функции freezeи cloneDeepсозданы для предотвращения дальнейших мутаций базового объекта состояния. Это сохраняет состояние пользовательского интерфейса более предсказуемым и в некоторой степени не имеющим состояния, поскольку данные перемещаются только в одном направлении.

Теперь перейдите к actions.jsфайлу и вставьте его на место:

export default state => {
  const addAuthor = author => {
    if (!author) return

    state.authors = [...state.authors, {
      ...author
    }]
  }

  const changeFilter = currentFilter => {
    state.currentFilter = currentFilter
  }

  return {
    addAuthor,
    changeFilter
  }
}

Это тестируемый объект JavaScript, который выполняет фактические изменения состояния. Для краткости я откажусь от написания модульных тестов, но оставлю это в качестве упражнения для читателей.

Чтобы запускать мутации из веб-компонентов, их необходимо зарегистрировать в глобальном window.applicationContextобъекте. Это делает этот объект состояния с изменениями доступным для остальной части приложения.

Откройте основной index.jsфайл и добавьте его прямо над местом, где я зарегистрировал настраиваемые элементы:

import observableFactory from './model/observable.js'
import actionsFactory from './model/actions.js'

const INITIAL_STATE = {
  authors: [],
  currentFilter: 'All'
}

const observableState = observableFactory(INITIAL_STATE)
const actions = actionsFactory(observableState)

window.applicationContext = Object.freeze({
  observableState,
  actions
})

Доступны два объекта: прокси observableStateи объект actionsс мутациями. Программа INITIAL_STATEзагружает приложение с исходными данными. Это то, что устанавливает начальное нулевое состояние конфигурации. Мутации действия принимают наблюдаемое состояние и запускают обновления для всех слушателей, внося изменения в observableStateобъект.

Поскольку мутации еще не подключены к веб-компонентам applicationContext, пользовательский интерфейс не отслеживает никаких изменений. Веб-компонентам потребуются атрибуты HTML для изменения и отображения данных состояния. Вот что будет дальше.

Observed Attributes

Для веб-компонентов изменения состояния можно отслеживать с помощью веб-API атрибутов. Это getAttribute, setAttributeи hasAttribute. С этим арсеналом более эффективно сохранять состояние пользовательского интерфейса в DOM.

Взломайте ObservableElement.jsи выпотрошите его, заменив этим кодом:

export default class ObservableElement extends HTMLElement {
  get authors() {
    if (!this.hasAttribute('authors')) return []

    return JSON.parse(this.getAttribute('authors'))
  }

  set authors(value) {
    if (this.constructor
      .observedAttributes
      .includes('authors')) {
      this.setAttribute('authors', JSON.stringify(value))
    }
  }

  get currentFilter() {
    if (!this.hasAttribute('current-filter')) return 'All'

    return this.getAttribute('current-filter')
  }

  set currentFilter(value) {
    if (this.constructor
      .observedAttributes
      .includes('current-filter')) {
      this.setAttribute('current-filter', value)
    }
  }

  connectAttributes () {
    window
      .applicationContext
      .observableState
      .addChangeListener(state => {
        this.authors = state.authors
        this.currentFilter = state.currentFilter
      })
  }

  attributeChangedCallback () {
    this.updateContent()
  }
}

Я специально использовал в current-filterатрибуте змеиный кожух. Это связано с тем, что веб-API атрибутов поддерживает только имена в нижнем регистре. Геттер / сеттер выполняет сопоставление между этим веб-API и тем, что ожидает класс, что является случаем верблюда.

connectAttributesМетод в веб — компонент добавляет свой собственный слушатель для отслеживания состояния мутаций. Есть attributeChangedCallbackдоступный, который срабатывает при изменении атрибута, а веб-компонент обновляет атрибут в DOM. Этот обратный вызов также вызывает, updateContentчтобы сообщить веб-компоненту об обновлении пользовательского интерфейса. Геттер / сеттер ES6 + объявляет те же свойства, что и в объекте состояния. Это то, что делает this.authors, например, доступным для веб-компонента.

Обратите внимание на использование constructor.observedAttributes. Это настраиваемое статическое поле, которое я могу объявить сейчас, чтобы родительский класс ObservableElementмог отслеживать, какие атрибуты важны для веб-компонента. Благодаря этому я могу выбирать, какая часть модели состояния имеет отношение к веб-компоненту.

Я воспользуюсь этой возможностью, чтобы конкретизировать остальную часть реализации, чтобы отслеживать и изменять состояние с помощью наблюдаемых в каждом веб-компоненте. Это то, что заставляет пользовательский интерфейс «оживать» при изменении состояния.

Вернитесь AuthorForm.jsи внесите эти изменения. Комментарии к коду скажут вам, где его разместить (или вы можете проконсультироваться с репозиторием ):

// This goes at top, right below the class declaration
static get observedAttributes() {
  return [
    'current-filter'
  ]
}

// In the Enter event handler, right above resetForm
this.addAuthor({
  name: inputs[0].value,
  email: inputs[1].value,
  topic: select.value === 'Topic' ? '' : select.value
})

// In the select event handler, rigth below console.log
this.changeFilter(e.target.value)

// At the very end of the connectedCallback method
super.connectAttributes()

// These helpers method go at the bottom of the class
addAuthor(author) {
  window
    .applicationContext
    .actions
    .addAuthor(author)
}

changeFilter(filter) {
  window
    .applicationContext
    .actions
    .changeFilter(filter)
}

updateContent() {
  // Capture state mutation to synchronize the search filter
  // with the dropdown for a nice effect, and reset the form
  if (this.currentFilter !== 'All') {
    this.form.querySelector('select').value = this.currentFilter
  }
  this.resetForm(this.form.querySelectorAll('input'))
}

В Jamstack вам может потребоваться вызвать внутренний API для сохранения данных. Я рекомендую использовать вспомогательные методы для этих типов вызовов. Как только постоянное состояние возвращается из API, оно может быть изменено в приложении.

Наконец, найдите AuthorGrid.jsи подключите наблюдаемые атрибуты (последний файл находится здесь ):

// This goes at top, right below the class declaration
static get observedAttributes() {
  return [
    'authors',
    'current-filter'
  ]
}

// At the very end of the connectedCallback method
super.connectAttributes()

// This helper method can go right above updateContent
getAuthorRow(author) {
  const {
    name,
    email,
    topic
  } = author

  const element = this.rowTemplate
    .content
    .firstElementChild
    .cloneNode(true)
  const columns = element.querySelectorAll('td')

  columns[0].textContent = name
  columns[1].textContent = email
  columns[2].textContent = topic

  if (this.currentFilter !== 'All'
    && topic !== this.currentFilter) {
    element.style.display = 'none'
  }

  return element
}

// Inside updateContent, at the very end
this.authors
  .map(a => this.getAuthorRow(a))
  .forEach(e => this.table
    .querySelector('tbody')
    .appendChild(e))

Каждый веб-компонент может отслеживать различные атрибуты в зависимости от того, что отображается в пользовательском интерфейсе. Это хороший чистый способ разделения компонентов, поскольку он имеет дело только с собственными данными состояния.

Попробуйте это в браузере. Взломайте инструменты разработчика и проверьте HTML. Вы увидите атрибуты, установленные в DOM, например current-filter, в корне веб-компонента. Когда вы щелкаете и нажимаете Enter, обратите внимание, что приложение автоматически отслеживает мутации состояния в DOM.

Gotchas

Для получения дополнительной информации не забудьте оставить инструменты разработчика открытыми, перейдите в отладчик JavaScript и найдите AuthorGrid.js. Затем установите точку останова в любом месте updateContent. Выберите поисковый фильтр. Заметили, что браузер повторяет этот код более одного раза? Это означает, что код, обновляющий пользовательский интерфейс, запускается не один раз, а каждый раз при изменении состояния.

Это из-за этого кода, который находится в ObservableElement:

window
  .applicationContext
  .observableState
  .addChangeListener(state => {
    this.authors = state.authors
    this.currentFilter = state.currentFilter
  })

В настоящее время существует ровно два слушателя, которые срабатывают при изменении состояния. Если веб-компонент отслеживает более одного свойства состояния, например this.authors, это вызывает гораздо больше обновлений пользовательского интерфейса. Это приводит к неэффективному обновлению пользовательского интерфейса и может вызвать задержку с достаточным количеством слушателей и изменениями в DOM.

Чтобы исправить это, откройте ObservableElement.jsи вернитесь к установщикам атрибутов HTML:

// This can go outside the observable element class
const equalDeep = (x, y) => JSON.stringify(x) === JSON.stringify(y)

// Inside the authors setter
if (this.constructor.observedAttributes.includes('authors')
  && !equalDeep(this.authors, value)) {

// Inside the currentFilter setter
if (this.constructor.observedAttributes.includes('current-filter')
  && this.currentFilter !== value) {

Это добавляет уровень защитного программирования для обнаружения изменений атрибутов. Когда веб-компонент понимает, что ему не нужно обновлять пользовательский интерфейс, он пропускает установку атрибута.

Теперь вернитесь в браузер с точкой останова, состояние обновления должно произойти updateContentтолько один раз.

Заключение

Приложения без фреймворка через веб-компоненты и наблюдаемые объекты позволяют создавать многофункциональные пользовательские интерфейсы без каких-либо зависимостей. Это делает полезную нагрузку приложения легкой и быстрой для клиентов.

Читайте также:  Создание форм с Django: Django в примерах
Оцените статью
bestprogrammer.ru
Добавить комментарий