如何在聚合物 3 组件中实现 lit-html 而不是 juicy-html?

How to implement lit-html instead of juicy-html in a polymer 3 component?

我正在将一个应用程序从 polymer 1 转换为 polymer 3。我使用了 juicy-html,但它们还没有更新到 Polymer 3,我看到有 lit-html。我想知道如何将此代码段更改为使用 lit-html。它用于扩展字符串,例如:'Hello <span class="highlight">world</span>!'

这是我的聚合物 1 组件的代码片段。

<template is="dom-repeat" items="[[item.snippets]]">
  <template is="dom-repeat" items="[[item.matches]]">
    <div><template is="juicy-html" content$="[[item.text]]"></template></div>
  </template>
</template>

我需要为内部 div 实现一个新组件吗?有没有我可以看的例子?

这是生成的 Polymer 3 元素,用于在字符串中显示突出显示的文本:

从'@polymer/lit-element/lit-element.js'导入{html, LitElement};

/**
 * `search-snippet-highlight`
 * 
 *
 * @customElement
 * @polymer
 * @demo demo/index.html
 */
class SearchSnippetHighlight extends LitElement {

  static get properties() {
    return {
      snippet: { type: String }
    };
  }

  render() {
    return html`
      <style>.highlight { background-color: yellow; }</style>
      <div .innerHTML="${this.sanitizeHtml(this.snippet)}"></div>`;
  }

  sanitizeHtml(input) {
    return input; // TODO: actually sanitize input with sanitize-html library
  }

}

window.customElements.define('search-snippet-highlight', SearchSnippetHighlight);

在 Polymer 的 LitElement(推荐使用 lit-html 的基本元素)中 <template>juicy-html 的等价物是:

render() {
  let content = '';
  for (const s of this.item.snippets) {
    for (const m of s.matches) {
      content += `<div>${m.text}</div>`;
    }
  }
  return html`<div .innerHTML="${this.sanitizeHtml(content)}"></div>`;
}

上面的 render 函数执行以下操作:

  1. 从输入构建一个 HTML 字符串
  2. 清理 HTML
  3. 将结果放入容器 divinnerHTML,使用 LitElement 语法 (.PROPERTY="VALUE")

<html>
<head>
  <!-- Polyfills only needed for Firefox and Edge. -->
  <script src="https://unpkg.com/@webcomponents/webcomponentsjs@latest/webcomponents-loader.js"></script>
</head>
<body>
  <!-- Works only on browsers that support Javascript modules like
       Chrome, Safari, Firefox 60, Edge 17 -->
  <script type="module">
    import {LitElement, html} from 'https://unpkg.com/@polymer/lit-element/lit-element.js?module';

    class MyElement extends LitElement {

      static get properties() {
        return {
          item: { type: Object }
        }
      }

      constructor() {
        super();
        this.item = {
          snippets: [
            {
              matches: [
                {text: 'hello <span class="highlight">world</span>'},
                {text: 'we are the <span class="highlight">world</span>'},
                {text: 'war of the <span class="highlight">world</span>s'},
              ]
            },
            {
              matches: [
                {text: 'the <span class="highlight">cat</span> in the hat'},
                {text: '<span class="highlight">cat</span>fish are in the water'},
                {text: '<span class="highlight">cat</span>erpillars become butterflies'},
              ]
            },
          ]
        };
      }

      render() {
        let content = '';
        for (const s of this.item.snippets) {
          for (const m of s.matches) {
            content += `<div>${m.text}</div>`;
          }
        }
        return html`
          <style>.highlight { background: rgb(255, 251, 222); }</style>
          <div .innerHTML="${this.sanitizeHtml(content)}"></div>`;
      }

      sanitizeHtml(input) {
        return input; // TODO: actually sanitize input with sanitize-html library
      }
    }

    customElements.define('my-element', MyElement);
  </script>
  
  <my-element mood="great"></my-element>
  
</body>
</html>

我把它放在一个答案中,因为 @tony19 建议它可以正确格式化。我并不是真的在问一个单独的问题,实际上只是在问一个修辞性的问题,而不是提供一个单独的解决方案。

您可以大大简化该方法,因为 lit 允许您通过 map 函数分层构建 html。

render() {    
    return html`
        <div>${this.item.snippets.map(s => s.matches.map(m => 
                html`<div>${this.sanitizeHtml(m.text)}</div>`
            ))}
       </div>
    `; 
}

现在这个(以及公认的答案)的一个问题是每次渲染你都要重新计算整个事情,包括清理 Html。 lit-html 有一个 guard 指令可以帮助解决这个问题。添加此选项将仅在更改时重新呈现。

render() {    
    return html`
        <div>${guard(this.item.snippets, () => this.item.snippets.map(s => 
                 guard(s.matches, () => s.matches.map(m => 
                html`<div>${this.sanitizeHtml(m.text)}</div>`
            ))))}
       </div>
    `; 
}

这确实需要您对 this.item.snippets 和基础 matches 的更新方式施加一些约束。您必须确保正在使用的 "Array" 引用在有更新时发生变化。像这样的东西(假设匹配项正在用新匹配项更新)并且 sindex 是您要更新的 snippet 的索引,而 mindex 是 [=21= 的索引] 在 snippet 中,您正在更新 newMatch;

this.items.snippets = this.items.snippets.map((snippet, index) => index === sindex ?
   {...snippet, matches: snippet.matches.map((match, index) => index === mindex ?
      newMatch : match)} : snippet);