【问题标题】:Polymer 1.0 Global VariablesPolymer 1.0 全局变量
【发布时间】:2015-06-15 16:03:12
【问题描述】:

在 Polymer 0.5 中,有关全局变量的建议如以下问题/答案中所述:

Polymer global variables

但是在 Polymer 1.0 中,这似乎不起作用。更改通知不会在底层模型上自动生成,而是在<dom-module> 上生成,这意味着更改通知将仅在<app-globals> 之一上生成。

在 Polymer 1.0 中实现此模式的推荐方法是什么?

【问题讨论】:

  • 这确实是个问题。我想我们需要使用外部变量观察解决方案并手动在元素中调用this.notifyPath。有没有流行的轻量级对象观察框架?
  • github.com/polymer/observe-js 所以我认为它会完成这项工作。我想知道它是否会被支持。我想他们应该写一篇关于在这种情况下使用什么的教程。
  • 评论有点晚了,但我想知道你的用例到底是什么?我也在将一个相当复杂的 0.5 应用程序升级到 1.0,发现我可以改用纯数据绑定来重新组织。
  • @zerodevx:是的,最后我也改变了我的应用程序以使用纯数据绑定。但是,这个问题对我来说仍然很有趣。

标签: javascript html polymer polymer-1.0


【解决方案1】:

聚合物元素<iron-meta> 也是一个选项。对我来说,这是最简单的解决方案。

【讨论】:

    【解决方案2】:

    我已将 Etherealones 的解决方案扩展为行为,并扩展了 Polymers 的“set”和“notifyPath”方法以自动触发更新。这是尽可能接近跨组件/元素的真正数据绑定:

    globals-behavior.html:

    <script>
    var instances = [];
    var dataGlobal = {};
    
    var GlobalsBehaviour = {
    
      properties: {
        globals: {
          type: Object,
          notify: true,
          value: dataGlobal
        }
      },
    
      ready: function() {
        var _setOrig = this.set;
        var _notifyPathOrig = this.notifyPath;
        this.set = function() {
          _setOrig.apply(this, arguments);
          if (arguments[0].split(".")[0] === "globals") {
            this.invokeInstances(_notifyPathOrig, arguments);
          }
        };
        this.notifyPath = function(path, value) {
          _notifyPathOrig.apply(this, arguments);
          if (arguments[0].split(".")[0] === "globals") {
            this.invokeInstances(_notifyPathOrig, arguments);
          }
        };
      },
    
      invokeInstances: function(fn, args) {
        var i;
        for (i = 0; i < instances.length; i++) {
          instance = instances[i];
          if (instance !== this) {
            fn.apply(instance, args);
          }
        }
      },
    
      attached: function() {
        instances.push(this);
      },
    
      detached: function() {
        var i;
        i = instances.indexOf(this);
        if (i >= 0) {
          instances.splice(i, 1);
        }
      }
    };
    
    </script>
    

    在所有应该可以访问全局变量的聚合物元素中:

      <script>
        Polymer({
          is: 'globals-enabled-element',
          behaviors: [GlobalsBehaviour]
        });
      </script>
    

    示例:

    1. 我已将完整示例发布为Gist on Github
    2. 这里有一个 sn-p 来查看它的实际效果:

        <!DOCTYPE html>
        <html>
          <head>
            <title>Globals Behavior Example</title>
            <link rel="import" href="//rawgit.com/Polymer/polymer/master/polymer.html">
            <dom-module id="globals-enabled-element">
              <template>
                <input type="text" value="{{globals.my_variable::input}}">
              </template>
              <script>
                var instances = [];
                var dataGlobal = {};
                
                var GlobalsBehaviour = {
                  
                  properties: {
                    globals: {
                      type: Object,
                      notify: true,
                      value: dataGlobal
                    }
                  },
                  
                  ready: function() {
                    var _setOrig = this.set;
                    var _notifyPathOrig = this.notifyPath;
                    this.set = function() {
                      _setOrig.apply(this, arguments);
                      if (arguments[0].split(".")[0] === "globals") {
                        this.invokeInstances(_notifyPathOrig, arguments);
                      }
                    };
                    this.notifyPath = function(path, value) {
                      _notifyPathOrig.apply(this, arguments);
                      if (arguments[0].split(".")[0] === "globals") {
                        this.invokeInstances(_notifyPathOrig, arguments);
                      }
                    };
                  },
                  
                  invokeInstances: function(fn, args) {
                    var i;
                    for (i = 0; i < instances.length; i++) {
                      instance = instances[i];
                      if (instance !== this) {
                        fn.apply(instance, args);
                      }
                    }
                  },
                  
                  attached: function() {
                    instances.push(this);
                  },
                  
                  detached: function() {
                    var i;
                    i = instances.indexOf(this);
                    if (i >= 0) {
                      instances.splice(i, 1);
                    }
                  }
                };
              </script>
              <script>
                Polymer({
                  is: 'globals-enabled-element',
                  behaviors: [GlobalsBehaviour]
                });
              </script>
            </dom-module>
          </head>
          <body>
            <template is="dom-bind">
              <p>This is our first polymer element:</p>
              <globals-enabled-element id="element1"></globals-enabled-element>
              <p>And this is another one:</p>
              <globals-enabled-element id="element2"></globals-enabled-element>
            </template>
          </body>
        </html>

    【讨论】:

    • 这很好。也许也可以为pushpopshiftunshiftsplice 找到方法? Polymer.push prototype 可能会有所帮助。显然有一个内部_notifySplice。不知道那个界面有多稳定。
    • 另外,我宁愿自己提供全局变量给元素,也许你应该期待变量名_globals,如果它不存在,则在Behavior.ready 中使用console.warn。单个 global 全局听起来不太好。
    • 我同意 - 将其作为一种行为实现表明它已准备好被不同的组件使用,但据我所知,情况并非如此。
    • 我无法让它与 Polymer 2.0 一起使用,知道您需要更新什么才能让它工作吗?
    【解决方案3】:

    我已经实现了一个类似iron-signals 的模式用于此目的。所以基本的原则是当更新发生时你手动通知其他实例。

    考虑一下:

    <dom-module id="x-global">
    <script>
    (function() {
      var instances = [];
    
      var dataGlobal = {};
    
      Polymer({
        is: 'x-global',
    
        properties: {
          data: {
            type: Object,
            value: dataGlobal,
          },
        },
    
        attached: function() {
          instances.push(this);
        },
    
        detached: function() {
          var i = instances.indexOf(this);
          if (i >= 0) {
            instances.splice(i, 1);
          }
        },
    
        set_: function(path, value) {
          this.set(path, value);
    
          instances.forEach(function(instance) {
            if (instance !== this) { // if it is not this one
              instance.notifyPath(path, value);
            }
          }.bind(this));
        },
    
        notifyPath_: function(path, value) {
          instances.forEach(function(instance) {
            instance.notifyPath(path, value);
          });
        },
    
        fire_: function(name, d) {
          instances.forEach(function(instance) {
            instance.fire(name, d);
          });
        },
      });
    })();
    </script>
    </dom-module>
    

    当您触发事件时,您将简单地调用具有下划线后缀的版本,例如 fire_。我猜你甚至可以使用这种模式创建某种聚合物行为。

    注意前面的下划线属性已经被 Polymer 使用,所以不要继续将它们转换为 _fire

    附: 我没有环顾四周解决如何反映this.push(array, value); 的通知,因为我不需要它。我不知道这种方式是否可行。应该去找Polymer.Base.push

    【讨论】:

    • 好方法!我已将其扩展为用作行为,将在此处发布作为答案。
    【解决方案4】:

    Polymer 的一位创建者 Sjmiles 刚刚将以下 snippet 发布到 Polymer slack room 作为共享数据的示例:

    <!doctype html>
    <html>
    <head>
    
      <meta charset="utf-8">
    
      <meta name="description" content="shared-data element and repeats">
    
      <base href="http://milestech.net/components/">
    
      <script href="webcomponentsjs/webcomponents-lite.min.js"></script>
      <link href="polymer/polymer.html" rel="import">
    
    </head>
    <body>
    
      <demo-test></demo-test>
    
      <script>
    
        (function() {
          var private_data = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
          Polymer({
            is: 'private-shared-data',
            properties: {
              data: {
                type: Object,
                notify: true,
                value: function() {
                  return private_data;
                }
              }
            }
          });
        })();
    
        Polymer({
          is: 'xh-api-device',
          properties: {
            data: {
              type: Array,
              notify: true
            },
            _share: {
              value: document.createElement('private-shared-data')
            }
          },
          observers: [
            'dataChanged(data.*)'
          ],
          ready: function() {
            this.data = this._share.data;
            this.listen(this._share, 'data-changed', 'sharedDataChanged');
          },
          dataChanged: function(info) {
            this._share.fire('data-changed', info, {bubbles: false});
          },
          sharedDataChanged: function(e) {
            this.fire(e.type, e.detail);
          },
          add: function(name) {
            this.push('data', {name: name});
          }
        });
    
      </script>
    
      <dom-module id="demo-test">
        <template>
    
          <h2>One</h2>
    
          <xh-api-device id="devices" data="{{data}}"></xh-api-device>
    
          <template is="dom-repeat" items="{{data}}">
            <div>name: <span>{{item.name}}</span></div>
          </template>
    
          <h2>Two</h2>
    
          <xh-api-device data="{{data2}}"></xh-api-device>
    
          <template is="dom-repeat" items="{{data2}}">
            <div>name: <span>{{item.name}}</span></div>
          </template>
    
          <br>
          <br>
    
          <button on-click="populate">Populate</button>
    
        </template>
        <script>
          Polymer({
            populate: function() {
              this.$.devices.add((Math.random()*100).toFixed(2));
              // this works too
              //this.push('data', {name: (Math.random()*100).toFixed(2)});
            }
          });
        </script>
      </dom-module>
    
    </body>
    </html>
    

    我实际上已经将我的应用程序转移到使用简单的数据绑定,所以我不确定这种方法的有效性,但它可能对某人有用。

    【讨论】:

    • 感谢您发布示例!起初我认为使用类似 0.5 的全局变量可能是一种反模式(因为在 1.0 文档中没有提到这一点),所以我对我的应用程序中的纯数据绑定进行了相当痛苦的更改。很高兴在 1.0 中也有实现全局 var 功能的方法。
    • 我认为这看起来有点过于复杂。所以我猜它也复制了this.push 触发的数组更改事件。
    • 对于我将变量从一个来源共享到其他元素的基本需求,这对我来说更有意义。我猜您可以根据需要将 data 从数组更改为对象?
    【解决方案5】:

    我试图改进 Alexei Volkov 的回答,但我想单独定义全局变量。我使用 observer 属性代替了 getter/setter,并将密钥与实例一起保存。

    用法是:

    <app-data  key="fName" data="{{firstName}}" ></app-data>
    

    keyproperty 定义了全局变量的名称。

    所以例如你可以使用:

    <!-- Output element -->
    <dom-module id="output-element" >
      <template>
        <app-data key="fName" data="{{data1}}" ></app-data>
        <app-data key="lName" data="{{data2}}" ></app-data>
        <h4>Output-Element</h4>
        <div>First Name: <span>{{data1}}</span></div>
        <div>Last Name: <span>{{data2}}</span></div>
      </template>
    </dom-module>
    
    <script>Polymer({is:'output-element'});</script>
    

    &lt;app-data&gt;dom 模块的定义:

    <dom-module id="app-data"></dom-module>
    <script>
    (function () {
        var instances = [];
        var vars = Object.create(Polymer.Base);
    
        Polymer({
            is: 'app-data',
            properties: {
               data: {
                    type: Object,
                    value: '',
                    notify: true,
                    readonly: false,
                    observer: '_data_changed'
                },
              key: String
            },
            created: function () {
              key = this.getAttribute('key');
              if (!key){
                console.log(this);
                throw('app-data element requires key');
              }
    
              instances.push({key:key, instance:this});
            },
    
            detached: function () {
                key = this.getAttribute('key');
                var i = instances.indexOf({key:key, instance:this});
                if (i >= 0) {
                    instances.splice(i, 1);
                }
            },
    
          _data_changed: function (newvalue, oldvalue) {
            key = this.getAttribute('key');
            if (!key){
                throw('_data_changed: app-data element requires key');
                }
            vars.set(key, newvalue);
    
            // notify the instances with the correct key
            for (var i = 0; i < instances.length; i++) 
            {
              if(instances[i].key == key)
              {
                instances[i].instance.notifyPath('data', newvalue);
              }
            }
          }
    
    
        });
    })();
    </script>
    

    完整的演示在这里:http://jsbin.com/femaceyusa/1/edit?html,output

    【讨论】:

    • 不像当前构建的那样,因为 Polymer 观察者不监视/传播数组元素上的更改事件。查看stackoverflow.com/a/30676440/1878199 了解如果您真的需要如何更改它的一些想法。我倾向于通过调整设计来减少全局变量。
    • 使用此解决方案时,我遇到了延迟加载组件的竞争情况。如发布的那样,延迟加载的组件不会使用共享数据中的值进行初始化。请参阅我的帖子以获取补丁。
    • 能否将其修改为使用,以便您可以将全局变量作为给定自定义元素的依赖项导入?
    • 出于好奇,您为什么使用this.getAttribute('key') 而不仅仅是this.key
    【解决方案6】:

    我已将以上所有建议合并到以下全局聚合物对象中

    <dom-module id="app-data">
    </dom-module>
    <script>
        (function () {
            var instances = [];
            var vars = Object.create(Polymer.Base);
            var commondata = {
                get loader() {
                    return vars.get("loader");
                },
                set loader(v) {
                    return setGlob("loader", v);
                }
            };
    
            function setGlob(path, v) {
                if (vars.get(path) != v) {
                    vars.set(path, v);
                    for (var i = 0; i < instances.length; i++) {
                        instances[i].notifyPath("data." + path, v);
                    }
                }
                return v;
            }
    
            Polymer({
                is: 'app-data',
                properties: {
                    data: {
                        type: Object,
                        value: commondata,
                        notify: true,
                        readonly: true
                    }
                },
                created: function () {
                    instances.push(this);
                },
    
                detached: function () {
                    var i = instances.indexOf(this);
                    if (i >= 0) {
                        instances.splice(i, 1);
                    }
                }
            });
        })();
    </script>
    

    然后像其他人一样使用它

    <dom-module id="app-navigation">
        <style>
    
        </style>
        <template>
            <app-data id="data01" data="{{data1}}" ></app-data>
            <app-data id="data02" data="{{data2}}"></app-data>
            <span>{{data1.loader}}</span>
            <span>{{data2.loader}}</span>
        </template>
    
    </dom-module>
    <script>
    
        (function () {
            Polymer({
                is: 'app-navigation',
                properties: {
                },
                ready: function () {
                    this.data1.loader=51;
                }
            });
        })();
    </script>
    

    更改 data1.loader 或 data2.loader 会影响其他实例。您应该扩展 commondata 对象以添加更多全局属性,如 loader 属性所示。

    【讨论】:

      【解决方案7】:

      如果您将应用程序包装在模板中,则更容易实现与全局变量相同的效果。观看video 中的解释(我链接到解释该概念的确切分秒)。

      【讨论】:

      • 您的链接未显示在帖子中。请用链接重新发布。
      【解决方案8】:

      使用ootwch's solution,我遇到了延迟加载组件的竞争情况。

      正如发布的那样,延迟加载的组件不会使用共享数据中的值进行初始化。

      如果其他人遇到同样的问题,我想我通过添加这样的就绪回调来解决它:

      ready: function() {
        const key = this.getAttribute('key')
        if (!key) {
          throw new Error('cm-app-global element requires key')
        }
      
        const val = vars.get(key)
        if (!!val) {
          this.set('data', val)
        }
      },
      

      希望这可以减轻一些人的痛苦。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-18
        • 2015-07-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多