繁体   English   中英

Web 组件,来回传递数据

[英]Web Components, pass data to and from

我的理解是,数据通过其属性传递到自定义 html 元素,并通过调度 CustomEvent 发送出去。

JavaScript 对象显然可以在事件的详细信息字段中发送出去,但是如果元素需要向其中传递大量数据怎么办。 有没有办法在 JavaScript 中为它提供 object。

例如,如果元素包含需要动态初始化或更改的可变数量的部分(例如,具有可变行数的表)怎么办? 我可以想象设置和修改由组件内部解析的 JSON 字符串组成的属性,但感觉这并不是一种优雅的处理方式:

<my-element tableRowProperties="[{p1:'v1', p2:'v2'}, {p1:'v1',p2:'v2'}, {p1:'v1',p2:'v2'}]"></my-element>

或者您可以让元素监听来自外部的包含数据负载的事件吗?

传入数据

如果您真的想要/需要将大量数据传递到您的组件中,那么您可以通过四种不同的方式做到这一点:

1)使用属性。 这是最简单的,因为您只需通过为元素提供值来传入对象,如下所示: el.data = myObj;

2)使用属性。 我个人很讨厌这种做法,但是有些框架需要通过属性传入数据。 这类似于您在问题中的显示方式。 <my-el data="[{a:1},{a:2}....]"></my-el> 请注意遵循与转义属性值相关的规则。 如果您使用此方法,您将需要在您的属性上使用JSON.parse ,这可能会失败。 在 HTML 中,在属性中显示大量数据也会变得非常难看。

3通过子元素传入。 想想带有<option>子元素的<select>元素。 您可以使用任何元素类型作为子元素,它们甚至不需要是真正的元素。 在您的connectedCallback函数中,您的代码只是获取所有子元素并将元素、它们的属性或它们的内容转换为您的组件需要的数据。

4使用提取。 为您的元素提供一个 URL 以获取其自己的数据。 想想<img src="imageUrl.png"/> 如果您已经拥有组件的数据,那么这似乎是一个糟糕的选择。 但是浏览器提供了一个很酷的嵌入数据的特性,它类似于上面的选项 2,但由浏览器自动处理。

以下是在图像中使用嵌入数据的示例:

 img { height: 32px; width: 32px; }
 <img src="data:image/svg+xml;charset=utf8,%3C?xml version='1.0' encoding='utf-8'?%3E%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 314.7 314.7'%3E%3Cstyle type='text/css'%3E .st0{fill:transparent;stroke:%23231F20;stroke-width:12;} .st1{fill:%23231F20;stroke:%23231F20;stroke-width:10;stroke-linejoin:round;stroke-miterlimit:10;} %3C/style%3E%3Cg%3E%3Ccircle class='st0' cx='157.3' cy='157.3' r='150.4'/%3E%3Cpolygon class='st1' points='108,76.1 248.7,157.3 108,238.6'/%3E%3C/g%3E%3C/svg%3E">

这是在 Web 组件中使用嵌入数据的示例:

 function readSrc(el, url) { var fetchHeaders = new Headers({ Accept: 'application/json' }); var fetchOptions = { cache: 'default', headers: fetchHeaders, method: 'GET', mode: 'cors' }; return fetch(url, fetchOptions).then( (resp) => { if (resp.ok) { return resp.json(); } else { return { error: true, status: resp.status } } } ).catch( (err) => { console.error(err); } ); } class MyEl extends HTMLElement { static get observedAttributes() { return ['src']; } attributeChangedCallback(attrName, oldVal, newVal) { if (oldVal !== newVal) { this.innerHtml = ''; readSrc(this, newVal).then( data => { this.innerHTML = `<pre> ${JSON.stringify(data,0,2)} </pre>`; } ); } } } // Define our web component customElements.define('my-el', MyEl);
 <!-- This component would go load its own data from "data.json" <my-el src="data.json"></my-el> <hr/> The next component uses embedded data but still calls fetch as if it were a URL. --> <my-el src="data:json,[{&quot;a&quot;:9},{&quot;a&quot;:8},{&quot;a&quot;:7}]"></my-el>

你可以使用 XHR 来做同样的事情,但如果你的浏览器支持 Web 组件,那么它可能支持 fetch。 如果你真的需要的话,有几个很好的 fetch polyfills。

使用选项 4 的最大优势是您可以从 URL 获取数据,并且可以直接嵌入数据。 这正是大多数预定义 HTML 元素(例如<img>工作方式。

更新

我确实想到了将 JSON 数据放入对象的第 5 种方法。 那是通过在组件中使用<template>标签。 这仍然需要您调用JSON.parse但它可以清理您的代码,因为您不需要太多地转义 JSON。

 class MyEl extends HTMLElement { connectedCallback() { var data; try { data = JSON.parse(this.children[0].content.textContent); } catch(ex) { console.error(ex); } this.innerHTML = ''; var pre = document.createElement('pre'); pre.textContent = JSON.stringify(data,0,2); this.appendChild(pre); } } // Define our web component customElements.define('my-el', MyEl);
 <my-el> <template>[{"a":1},{"b":"&lt;b>Hi!&lt;/b>"},{"c":"&lt;/template>"}]</template> </my-el>

传出数据

从组件中获取数据的方法有以下三种:

1) 从属性中读取值。 这是理想的,因为属性可以是任何东西,并且通常采用您想要的数据格式。 属性可以返回字符串、对象、数字等。

2) 读取属性。 这要求组件保持属性最新,并且可能不是最佳的,因为所有属性都是字符串。 因此,您的用户需要知道他们是否需要对您的值调用JSON.parse

3) 事件。 这可能是添加到组件中最重要的事情。 当组件中的状态改变时,事件应该被触发。 事件应该基于用户交互触发,并且只是提醒用户发生了某些事情或某些事情可用。 传统上,您会在事件中包含相关数据。 这减少了组件用户需要编写的代码量。 是的,他们仍然可以读取属性或属性,但是如果您的事件包含所有相关数据,那么他们可能不需要做任何额外的事情。

有第 6 种方式与上面@Intervalia 的答案非常相似,但使用<script>标签而不是<template>标签。

这与Markdown Element使用的方法相同。

 class MyEl extends HTMLElement { connectedCallback() { var data; try { data = JSON.parse(this.children[0].innerHTML); } catch(ex) { console.error(ex); } this.innerHTML = ''; var pre = document.createElement('pre'); pre.textContent = JSON.stringify(data,0,2); this.appendChild(pre); } } // Define our web component customElements.define('my-el', MyEl);
 <my-el> <script type="application/json">[{"a":1},{"b":"<b>Hi!</b>"},{"c":"</template>"}]</script> </my-el>

我知道这已经得到回答,但这是我采取的一种方法。 我知道这不是火箭科学,可能有理由这样做; 但是,对我来说,这很管用。

这是一种传递数据的间接方法,其中在自定义元素中传递名为 wc_data 的属性,这是一个可以使用一次的“键”。

您显然可以对 wc-data 执行任何操作,例如回调和“callins”到自定义标签中。

链接到codesandbox

文件:

wc_data.ts

export const wc_data: {
    [name: string]: any,
    get(key: string): any,
    set(key: string, wc_data: any): any
} = {

    get(key: string): any {
        const wc_data = this[key];
        delete this[key];
        return wc_data;
    },

    set(p_key: string, wc_data: any) {
        this[p_key] = wc_data;
    }

}

自定义标签.ts

import { wc_data } from './wc_data';

const template = document.createElement('template');

template.innerHTML = `

    <style>
        .custom-tag {
            font-size: 1.6em;
        }
    </style>

    <button class="custom-tag">Hello <span name="name"></span>, I am your <span name="relation"></span></button>
`;

class CustomTag extends HTMLElement {

    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.appendChild(template.content.cloneNode(true));
    }

    callin() {
        console.log('callin called');
    }

    connectedCallback() {
        const v_wc_data = wc_data.get(this.getAttribute('wc-data'));
        console.log('wc_data', v_wc_data);

        const v_name = this.shadowRoot.querySelector('[name="name"]');
        const v_relation = this.shadowRoot.querySelector('[name="relation"]');

        v_name.innerHTML = v_wc_data.name;
        v_relation.innerHTML = v_wc_data.relation;

        const v_button = this.shadowRoot.querySelector('button');

        v_button.style.color = v_wc_data.color;

        v_wc_data.element = this;

        v_button.addEventListener('click', () => v_wc_data.callback?.());

    }

    disconnectedCallback() {
    }
}

window.customElements.define('custom-tag', CustomTag);

console.log('created custom-tag element');

export default {};

一些TsFile.ts

wc_data.set('tq', {
    name: 'Luke',
    relation: 'father',
    color: 'blue',
    element: undefined,
    callback() {
        console.log('the callback worked');
        const v_tq_element = this.element;
        console.log(this.element);
        v_tq_element.callin();
    },
});

一些 html..

<div>stuff before..</div>

<custom-tag wc_data="tq" />

<div>stuff after...</div>

如果您使用的是基于 Polymer 的 Web 组件,则数据的传递可以通过数据绑定来完成。 数据可以存储为 JSON 字符串,并通过上下文变量传递。

 <p>JSON Data passed via HTML attribute into context variable of and populating the variable into combobox.</p> <dom-bind><template> <iron-ajax url='data:text/json;charset=utf-8, [{"label": "Hydrogen", "value": "H"} ,{"label": "Oxygen" , "value": "O"} ,{"label": "Carbon" , "value": "C"} ]' last-response="{{lifeElements}}" auto handle-as="json"></iron-ajax> <vaadin-combo-box id="cbDemo" label="Label, value:[[cbDemoValue]]" placeholder="Placeholder" items="[[lifeElements]]" value="{{ cbDemoValue }}" > <template> [[index]]: [[item.label]] <b>[[item.value]]</b> </template> </vaadin-combo-box> <vaadin-combo-box label="Disabled" disabled value="H" items="[[lifeElements]]"></vaadin-combo-box> <vaadin-combo-box label="Read-only" readonly value="O" items="[[lifeElements]]"></vaadin-combo-box> <web-elemens-loader selection=" @polymer/iron-ajax, @vaadin/vaadin-element-mixin/vaadin-element-mixin, @vaadin/vaadin-combo-box, "></web-elemens-loader> </template></dom-bind> <script src="https://cdn.xml4jquery.com/web-elements-loader/build/esm-unbundled/node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script><script type="module" src="https://cdn.xml4jquery.com/web-elements-loader/build/esm-unbundled/src/web-elemens-loader.js"></script>

使用像Lego这样的小库可以让你编写以下内容:

<my-element :tableRowProperties="[{p1:'v1', p2:'v2'}, {p1:'v1',p2:'v2'}, {p1:'v1',p2:'v2'}]"></my-element>

并在您的my-element.html网络组件中:

<template>
  <table>
    <tr :for="row in state.tableRowProperties">
      <td>${row.p1}</td>
      <td>${row.p2}</td>
    </tr>
</template>

感谢其他贡献者,我想出了这个看起来更简单的解决方案。 没有json解析。 我使用此示例将整个组件包装在 a-href 中以使该块可点击:


  customElements.define('ish-marker', class extends HTMLElement {
    constructor() {
      super()
      const template = document.getElementById('ish-marker-tmpl').content

      const wrapper = document.createElement("a")
      wrapper.appendChild( template.cloneNode(true) )
      wrapper.setAttribute('href', this.getAttribute('href'))

      const shadowRoot = this.attachShadow({mode: 'open'}).appendChild( wrapper )
    }
  })


<ish-marker href="https://go-here.com">
  ...
  // other things, images, buttons.
  <span slot='label'>Click here to go-here</span>
</ish-marker>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM