使用Polymer在同一Web组件的实例之间进行通信的最佳方式?

Alb*_*dzM 11 web-component polymer polymer-1.0 polymer-2.x

我正在尝试在同一元素的实例之间同步我的一些Web组件属性,因此如果其中一个属性发生更改,则相同的属性将在具有相应绑定和事件的所有实例中更新.

注意:我想将Polymer Data System Concepts用于实例之间的通信.

我-element.html

<dom-module id="my-element">
  <script>
    Polymer({
      is: 'my-element',

      properties: {
        myProp: {
          type: String,
          notify: true
      }
    });
  </script>
</dom-module>
Run Code Online (Sandbox Code Playgroud)

我 - 其他 - element.html

<dom-module id="my-other-element">
  <template>
    <my-element my-prop="{{otherProp}}"></my-element>
  </template>
  <script>
    Polymer({
      is: 'my-other-element',
      properties: {
        otherProp: {
          type: String,
          notify: true,
          readOnly: true
        }
      }
    })
  </script>
</dom-module>
Run Code Online (Sandbox Code Playgroud)

我-app.html

<dom-module id="my-app">
  <template>
    <my-element id="element"></my-element>
    <my-other-element id="otherElement"
      on-other-prop-changed="onPropChanged"
    ></my-other-element>
  </template>
  <script>
    Polymer({
      is: 'my-app',

      attached: function () {
        // should set 'myProp' to 'test' and trigger
        // the event 'my-prop-changed' in all my-element instances
        this.$.element.myProp = 'test'
      },

      onPropChanged: function (ev, detail) {
        console.log(detail.value); // should print 'test'
        console.log(this.$.element.myProp); // should print 'test'
        console.log(this.$.otherElement.otherProp); // should print 'test'
      }
    });
  </script>
</dom-module>
Run Code Online (Sandbox Code Playgroud)

PD:使用标准模式和良好实践会很好.

ale*_*esc 4

太长了;博士

我创建了一个自定义行为,可以同步所有具有notify: true. 工作原型:JSBin

目前,此原型不区分不同类型的元素,这意味着它只能同步同一自定义元素的实例 - 但这可以轻松更改。

您还可以定制行为,以便仅同步所需的属性,而不仅仅是所有与notify: true. 但是,如果您采用此路径,请注意您要同步的所有属性都必须具有notify: true,因为该行为会监听<property-name>-changed事件,只有当属性具有 时才会触发该事件notify: true

细节

让我们从自定义SyncBehavior行为开始:

(function() {
    var SyncBehaviorInstances = [];
    var SyncBehaviorLock = false;

    SyncBehavior = {
        attached: function() {
            // Add instance
            SyncBehaviorInstances.push(this);

            // Add listeners
            for(var property in this.properties) {
                if('notify' in this.properties[property] && this.properties[property].notify) {
                    // Watch all properties with notify = true
                    var eventHanler = this._eventHandlerForPropertyType(this.properties[property].type.name);
                    this.listen(this, Polymer.CaseMap.camelToDashCase(property) + '-changed', eventHanler);
                }
            }
        },

        detached: function() {
            // Remove instance
            var index = SyncBehaviorInstances.indexOf(this);
            if(index >= 0) {
                SyncBehaviorInstances.splice(index, 1);
            }

            // Remove listeners
            for(var property in this.properties) {
                if('notify' in this.properties[property] && this.properties[property].notify) {
                    // Watch all properties with notify = true
                    var eventHanler = this._eventHandlerForPropertyType(this.properties[property].type.name);
                    this.unlisten(this, Polymer.CaseMap.camelToDashCase(property) + '-changed', eventHanler);
                }
            }
        },

        _eventHandlerForPropertyType: function(propertyType) {
            switch(propertyType) {
                case 'Array':
                    return '__syncArray';
                case 'Object':
                    return '__syncObject';
                default:
                    return '__syncPrimitive';
            }
        },

        __syncArray: function(event, details) {
            if(SyncBehaviorLock) {
                return; // Prevent cycles
            }

            SyncBehaviorLock = true; // Lock

            var target = event.target;
            var prop = Polymer.CaseMap.dashToCamelCase(event.type.substr(0, event.type.length - 8));

            if(details.path === undefined) {
                // New array -> assign by reference
                SyncBehaviorInstances.forEach(function(instance) {
                    if(instance !== target) {
                        instance.set(prop, details.value);
                    }
                });
            } else if(details.path.endsWith('.splices')) {
                // Array mutation -> apply notifySplices
                var splices = details.value.indexSplices;

                // for all other instances: assign reference if not the same, otherwise call 'notifySplices'
                SyncBehaviorInstances.forEach(function(instance) {
                    if(instance !== target) {
                        var instanceReference = instance.get(prop);
                        var targetReference = target.get(prop);

                        if(instanceReference !== targetReference) {
                            instance.set(prop, targetReference);
                        } else {
                            instance.notifySplices(prop, splices);
                        }
                    }
                });
            }

            SyncBehaviorLock = false; // Unlock
        },

        __syncObject: function(event, details) {
            var target = event.target;
            var prop = Polymer.CaseMap.dashToCamelCase(event.type.substr(0, event.type.length - 8));

            if(details.path === undefined) {
                // New object -> assign by reference
                SyncBehaviorInstances.forEach(function(instance) {
                    if(instance !== target) {
                        instance.set(prop, details.value);
                    }
                });
            } else {
                // Property change -> assign by reference if not the same, otherwise call 'notifyPath'
                SyncBehaviorInstances.forEach(function(instance) {
                    if(instance !== target) {
                        var instanceReference = instance.get(prop);
                        var targetReference = target.get(prop);

                        if(instanceReference !== targetReference) {
                            instance.set(prop, targetReference);
                        } else {
                            instance.notifyPath(details.path, details.value);
                        }
                    }
                });
            }
        },

        __syncPrimitive: function(event, details) {
            var target = event.target;
            var value = details.value;
            var prop = Polymer.CaseMap.dashToCamelCase(event.type.substr(0, event.type.length - 8));

            SyncBehaviorInstances.forEach(function(instance) {
                if(instance !== target) {
                    instance.set(prop, value);
                }
            });
        },
    };
})();
Run Code Online (Sandbox Code Playgroud)

请注意,我使用了 IIFE 模式来隐藏保存自定义元素的所有实例的变量my-element。这是必要的,所以不要改变它。

正如您所看到的,该行为由六个函数组成,即:

  1. attached,它将当前实例添加到实例列表中,并使用 注册所有属性的侦听器notify: true
  2. detached,这会从实例列表中删除当前实例,并删除带有 的所有属性的侦听器notify: true
  3. _eventHandlerForPropertyType,它返回函数 4-6 之一的名称,具体取决于属性类型。
  4. __syncArray,它在实例之间同步数组类型属性。请注意,我忽略当前目标并实现一个简单的锁定机制以避免循环。该方法处理两种情况:分配一个新数组,以及改变现有数组。
  5. __syncObject,它在实例之间同步对象类型属性。请注意,我忽略当前目标并实现一个简单的锁定机制以避免循环。该方法处理两种情况:分配新对象和更改现有对象的属性。
  6. __syncPrimitive,它在实例之间同步属性的原始值。请注意,我忽略了当前目标以避免循环。

为了测试我的新行为,我创建了一个示例自定义元素:

<dom-module id="my-element">
    <template>
        <style>
            :host {
                display: block;
            }
        </style>

        <h2>Hello [[id]]</h2>
        <ul>
            <li>propString: [[propString]]</li>
            <li>
                propArray:
                <ol>
                    <template is="dom-repeat" items="[[propArray]]">
                        <li>[[item]]</li>
                    </template>
                </ol>
            </li>
            <li>
                propObject:
                <ul>
                    <li>name: [[propObject.name]]</li>
                    <li>surname: [[propObject.surname]]</li>
                </ul>
            </li>
        </ul>
    </template>

    <script>
        Polymer({
            is: 'my-element',
            behaviors: [
                SyncBehavior,
            ],
            properties: {
                id: {
                    type: String,
                },
                propString: {
                    type: String,
                    notify: true,
                    value: 'default value',
                },
                propArray: {
                    type: Array,
                    notify: true,
                    value: function() {
                        return ['a', 'b', 'c'];
                    },
                },
                propObject: {
                    type: Object,
                    notify: true,
                    value: function() {
                        return {'name': 'John', 'surname': 'Doe'};
                    },
                },
            },
            pushToArray: function(item) {
                this.push('propArray', item);
            },
            pushToNewArray: function(item) {
                this.set('propArray', [item]);
            },
            popFromArray: function() {
                this.pop('propArray');
            },
            setObjectName: function(name) {
                this.set('propObject.name', name);
            },
            setNewObjectName: function(name) {
                this.set('propObject', {'name': name, 'surname': 'unknown'});
            },
        });
    </script>
</dom-module>
Run Code Online (Sandbox Code Playgroud)

它具有 1 个 String 属性、1 个 Array 属性和 1 个 Object 属性;全部与notify: true. 自定义元素也实现该SyncBehavior行为。

要将上述所有内容组合到一个工作原型中,您只需执行以下操作:

<template is="dom-bind">
    <h4>Primitive type</h4>
    propString: <input type="text" value="{{propString::input}}" />

    <h4>Array type</h4>
    Push to propArray: <input type="text" id="propArrayItem" /> <button onclick="_propArrayItem()">Push</button> <button onclick="_propNewArrayItem()">Push to NEW array</button> <button onclick="_propPopArrayItem()">Delete last element</button>

    <h4>Object type</h4>
    Set 'name' of propObject: <input type="text" id="propObjectName" /> <button onclick="_propObjectName()">Set</button> <button onclick="_propNewObjectName()">Set to NEW object</button> <br />

    <script>
        function _propArrayItem() {
            one.pushToArray(propArrayItem.value);
        }

        function _propNewArrayItem() {
            one.pushToNewArray(propArrayItem.value);
        }

        function _propPopArrayItem() {
            one.popFromArray();
        }

        function _propObjectName() {
            one.setObjectName(propObjectName.value);
        }

        function _propNewObjectName() {
            one.setNewObjectName(propObjectName.value);
        }
    </script>

    <my-element id="one" prop-string="{{propString}}"></my-element>
    <my-element id="two"></my-element>
    <my-element id="three"></my-element>
    <my-element id="four"></my-element>
</template>
Run Code Online (Sandbox Code Playgroud)

在此原型中,我创建了四个my-element. 其中一个已propString绑定到输入,而其他则根本没有任何绑定。我创建了一个简单的表格,涵盖了我能想到的所有场景:

  • 更改原始值。
  • 将项目推送到数组。
  • 创建一个新数组(包含一项)。
  • 从数组中删除一个项目。
  • 设置对象属性。
  • 创建一个新对象。

编辑

我更新了我的帖子和原型,以解决以下问题:

  • 非原始值的同步,即数组和对象。
  • 正确地将属性名称从 Dash 大小写转换为 Camel 大小写(反之亦然)。

  • 感谢您的编辑!我一直在调试和测试这种行为。这就是我发现的,每次使用“instance.set(prop,details.value);”时,您都会用更改后的指针替换实例数组指针,导致所有元素实例共享相同的数组,从而导致错误“聚合物拼接”与物体的情况相同。另外,如果您可以修复第一段中的拼写错误,将“&lt;property-name&gt;-change”改为“&lt;property-name&gt;-changed”(它不允许我编辑它)。仍在调试和测试,但看起来没问题。当我完成测试时会接受答案。 (2认同)