Какие части должны идти в тени DOM и света DOM? - PullRequest
0 голосов
/ 22 января 2019

Я изучаю веб-компоненты.При разработке пользовательского элемента я должен решить, что будет скрыто в теневом DOM.Остальная часть будет затем отображаться в светлом DOM.

Насколько я понимаю, API допускают два крайних варианта использования с различными компромиссами:

  • почти ничего не скрывают в теневом DOMбольшая часть содержимого элемента находится в легком DOM и в атрибутах элемента:
    • это позволяет автору HTML предоставлять что-либо для отображения компонентом без записи JS;
    • это близко кстатус-кво в отношении возможности поиска и доступности
    • , но за проделанную работу вознаграждение мало;Я добавляю сложности с компонентами, но они ничего не инкапсулируют (все открыто).
  • скрыть почти все в теневом DOM, элемент innerHTML пуст:
    • это требует, чтобы элемент создавался из JS;
    • это блокирует использование намного больше, потому что создание экземпляров из JS более строго (по типу), чем использование HTML-слотов и атрибутов;
    • это может быть менее доступным для поиска и доступным (я не уверен, так ли это);

В настоящее время я склонен скрывать все в тениDOM по следующим причинам:

  • Я намерен создать экземпляр из JS.Я не собираюсь создавать страницы в HTML вручную.Было бы больше работы, чтобы кодировать и HTML API, и JS API.
  • Это менее познавательная работа, чтобы скрыть все.Мне не нужно находить правильный баланс о том, какая информация видна в светлом DOM.
  • Это ближе к большинству платформ JS, с которыми я знаком.

Я пропустилчто-то?


Редактировать

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

Я добавлю пример для каждого предела спектра:

Легкий DOM-тяжелый компонент: пользователь компонента должен вставить элементы в слоты
<template id=light-email-view>
  <div>
    <div><slot name=from></slot></div>
    <ul><slot name=to></slot></ul>
    <h1><slot name=subject></slot></h1>
    <div><slot name=content></slot></div>
    <ul><slot name=attachements></slot></ul>
    <div class=zero-attachment-fallback>no attachments</div>
  </div>
</template>

Теневой DOM-тяжелый компонент: пользователь компонента должен использовать JSAPI
<template id=shadow-email-view>
  <div></div>
</template>
<script>
...
let view = document.createElement('shadow-email-view');
// this method renders the email in the shadow DOM entirely
view.renderFromOject(email);
container.appendChild(view);
</script>

В первом примере автору компонента еще предстоит проделать большую работу, поскольку ему необходимо «проанализировать» DOM: им нужно подсчитать вложения, чтобы переключить запасной вариант;в основном, любое преобразование ввода, которое не выполняется браузером, копирующим элемент из светлого DOM в соответствующий слот теневого DOM.Затем им нужно прослушивать изменения атрибутов и еще много чего.Пользователь компонента также имеет больше работы, он должен вставить правильные элементы в правильные слоты, некоторые из них нетривиальны (содержание электронной почты может быть связано с ссылками).

Во втором примере компонентавтору не нужно реализовывать поддержку создания экземпляров из HTML со слотами.Но пользователь компонента должен создать экземпляр из JS. Все рендеринг выполняется в методе .renderFromObject автором компонента.Некоторые дополнительные методы предоставляют хуки для обновления представления, если это необходимо.

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

Итак, ставить все с теневым DOM жизнеспособным или должно Я предоставляю слоты, потому что это не соответствует стандарту, и мой код будет зависать от ожидающего их пользовательского агента (игнорируя старые UA, которые вообще не знают о пользовательских элементах)?

Ответы [ 3 ]

0 голосов
/ 22 января 2019

@ supersharp прибил его.

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

Давайте рассмотрим некоторыенативные элементы:

<form> нет теневого DOM, и единственное, что он делает - читает значения из дочерних элементов формы, чтобы иметь возможность выполнять HTTP GET, POST и т. д.

<video> 100% shadowDOM, и единственное, для чего оно использует дочерние приложения, это определить, какое видео будет воспроизводиться.Пользователь не может настроить любой CSS для теневых потомков тега <video>.И они не должны быть допущены.Единственное, что позволяет тег <video>, - это возможность скрывать или показывать этих теневых детей.Тег <audio> делает то же самое.

<h1> до <h6> Нет тени.Все, что это делает, это устанавливает размер шрифта по умолчанию и отображает дочерние элементы.

Тег <img> использует теневые дочерние элементы для отображения изображения и Alt-Text.

Как сказал @supersharpиспользование shadowDOM основано на элементе.Я бы сказал, что shadowDOM должен быть хорошо продуманным выбором.Я добавлю, что вам нужно помнить, что это должны быть компоненты, а не приложения.

Да, вы можете инкапсулировать все свое приложение в один компонент, но браузеры не пытались сделать это с нативными компонентами.,Чем более специализированными вы можете сделать свои компоненты для их повторного использования.

Избегайте добавления что-нибудь в ваши веб-компоненты, которые не являются vanilla JS, другими словами, не добавляйте какой-либо каркасный код вваши компоненты, если вы никогда не хотите делиться ими с кем-то, кто не использует эту платформу.Компоненты, которые я пишу, являются на 100% Vanilla JS и не содержат фреймворков CSS.И они используются в Angular, React и vue с без изменений в коде.

Но выбрал использование shadowDOM для каждого написанного компонента.И, если вам нужно работать в браузере, который изначально не поддерживает веб-компоненты, вы можете вообще не использовать shadowDOM.

И последнее.Если вы пишете компонент, который не использует shadowDOM, но у него есть CSS, тогда вы должны быть осторожны, когда размещаете CSS, поскольку ваш компонент может быть помещен в чужой shadowDOM.Если ваш CSS был помещен в тег <head>, он потерпит неудачу внутри другого shadowDOM.Я использую этот код для предотвращения этой проблемы:

function setCss(el, styleEl) {
  let comp = (styleEl instanceof DocumentFragment ? styleEl.querySelector('style') : styleEl).getAttribute('component');
  if (!comp) {
    throw new Error('Your `<style>` tag must set the attribute `component` to the component name. (Like: `<style component="my-element">`)');
  }

  let doc = document.head; // If shadow DOM isn't supported place the CSS in `<head>`
  // istanbul ignore else
  if (el.getRootNode) {
    doc = el.getRootNode();
    // istanbul ignore else
    if (doc === document) {
      doc = document.head;
    }
  }

  // istanbul ignore else
  if (!doc.querySelector(`style[component="${comp}"]`)) {
    doc.appendChild(styleEl.cloneNode(true));
  }
}

export default setCss;
0 голосов
/ 23 января 2019

Хорошо. Отложив на минутку, что я думаю, что это плохая сомнительная идея, вот код, который должен делать то, что вы хотите (я не запускал его, но он должен работать):

class FooElement extends HTMLElement {
  constructor () {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.appendChild(document.importNode(template.content, true));
  }

  _xformObject (object) {
    // turn the obj into DOM nodes
  }

  renderFromObject (object) {
    // you may need to do something fancier than appendChild,
    // you can always query the shadowRoot and insert it at
    // a specific point in shadow DOM
    this.shadowRoot.appendChild(this._xformObject(object));
  }
}

Вы, конечно, должны будете зарегистрировать пользовательский элемент.

Теперь иногда вы действительно не можете уйти от подобных действий. Но это должно быть абсолютное последнее средство . Смотрите ниже:

Почему я думаю, что это плохая сомнительная идея и как ее улучшить:

Одним из основных преимуществ веб-компонентов является то, что они допускают декларативную разметку HTML, а не процедурные манипуляции с JS DOM. Хотя предоставление API, о котором вы говорите, безусловно, является большим шагом вперед, например. создание таблицы путем создания узла таблицы, создания узла строки, создания некоторых тд, добавления их к строке, а затем добавления этого к таблице, я (и я думаю, что большинство) разработчиков считают, что если ваш пользовательский элемент требует непосредственного манипулирования JavaScript пользователем, тогда это на самом деле не элемент HTML: это интерфейс JavaScript.

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

// good
myCustomElement.setAttribute("toggled-on", true);

// this isn't *bad*, but don't *force* people to do this
myCustomElement.toggleState();

Возможно, вы по-прежнему захотите предоставить второй как часть вашего общедоступного API для удобства пользователей, но , требующий , кажется за гранью. Теперь одна проблема заключается в том, что вы, очевидно, не можете легко передать сложные структуры данных в атрибут HTML (у Polymer есть помощники для этого, если вы используете Polymer).

Но если это так, то вместо того, чтобы быть частью API элемента, я бы предоставил отдельную функцию, которая возвращает соответствующую структуру DOM, а не заполняет ее элементом. Вы даже можете сделать это методом класса вашего класса пользовательских элементов, если вы так катитесь.

Рассмотрим случай, когда у вас есть элемент List, который отображает произвольное количество элементов Item. Я думаю, что это здорово, предоставить удобный метод, который принимает массив и обновляет (легкий) DOM. Но пользователи должны иметь возможность добавлять их напрямую.

Ваш вариант использования может потребовать взлома вокруг этой проблемы. Иногда вам действительно нужно использовать антипаттерн. Но подумайте внимательно, так ли это в вашей стихии.

0 голосов
/ 22 января 2019

Выбор на 100% зависит от варианта использования.

Также:

  • если вы хотите, чтобы пользователь мог форматировать ваш пользовательский элемент с глобальными атрибутами стиля CSS, вы можете выбрать обычный, легкий DOM.

  • вы правы: в Shadow DOM "это может быть менее доступно для поиска": метод document.querySelector() не будет проверять содержимое Shadow DOM.

  • как следствие, некоторые JS-библиотеки третьего уровня могут не легко интегрироваться с Shadow DOM

  • если вы намереваетесь использовать полизаполнение Custom Element для устаревших браузеров, вы можете избегать Shadow DOM, поскольку некоторые его функции не могут быть полностью заполнены.

  • во многих случаях ответ заключается в предоставлении комбинации Light DOM и Shadow DOM. По предложению @JaredSmith:

    • Shadow DOM для автора веб-компонента,
    • Легкий DOM для пользователя Web Compoent, интегрированный в Shadow DOM с <slot>.

В заключение вам следует учитывать контекст, в котором будет использоваться ваш веб-компонент, чтобы решить, требуется ли Shadow DOM или нет.


Ответ на редактирование

Учитывая ваш вариант использования, я бы создал пользовательский элемент и:

  • позволяет пользователю заполнять легкую модель DOM атомарными значениями: элемент типа <div class="mail-to"> или пользовательские подкомпоненты <mail-to>, как предложено @Intervalia,
  • используйте Shadow DOM, чтобы замаскировать светлый DOM,
  • используйте Javascript: this.querySelectorAll('.mail-to') или this.querySelectorAll('mail-to') вместо <slot> для извлечения данных из светлого DOM и копирования (или перемещения) в Shadow DOM.

Таким образом, пользователям не придется изучать <slot> работу, и разработчик сможет форматировать рендеринг веб-компонента с большей свободой.

<email-view>
  <mail-to>guillaume@stackoverflow.com</mail-to>
  <mail-to>joe@google.fr</mail-to>
  <mail-from>supersharp@cyber-nation.fr</mail-from>
  <mail-body>hello world!</mail-body>
<email-view>
...