【问题标题】:Access to component property inside a component method called by an event访问由事件调用的组件方法内的组件属性
【发布时间】:2018-07-20 13:31:01
【问题描述】:

我有一个组件,当在任意位置单击窗口时会切换。

为了实现这一点,我将一个组件方法作为全局事件绑定到 body 元素。

此外,我需要访问组件方法中的组件属性,该属性由绑定到body 元素的点击事件调用。

但它不起作用:

app/components/example-component.js:

import Ember from 'ember';

export default Ember.Component.extend({

  exampleProperty: 'example value',

  didInsertElement() {
    console.log('start didInsertElement() ************************************************************');
    console.log('- "this" is the Ember.js component:');
    console.log(this.$());
    console.log('- "this.exampleProperty" has the Ember.js component value:');
    console.log(this.exampleProperty);
    Ember.$('body').click(this.exampleMethod).click();
    console.log('end didInsertElement() ************************************************************');
  },
  exampleMethod(event) {
    console.log('start exampleMethod() ************************************************************');
    console.log('- "this" is the DOM element that has triggered the event:');
    console.log(Ember.$(this));
    // console.log(this.$()); This doesn't work
    console.log('- "this.exampleProperty" is undefined:');
    console.log(this.exampleProperty);
    console.log('end exampleMethod() ************************************************************');
  },
});

如何让它工作?

【问题讨论】:

    标签: javascript jquery ember.js components


    【解决方案1】:

    不确定它是否提供了您正在寻找的更清洁的方法,但.bind(this) 可能会有所帮助。

    // Using bind:
    Ember.$('body').click(this, this.exampleMethod.bind(this)).click();
    
    // Results in the below working as you desire:
    this.$(); //returns jQuery.fn.init [div#ember256.ember-view, context: div#ember256.ember-view]
    
    Ember.get(this, 'exampleProperty'); //returns 'example value'
    

    【讨论】:

    • 效果很好!实际上只有.bind(this) 函数是必需的,this 上下文不再是必需的。那么:Ember.$('body').click(this.exampleMethod.bind(this)).click(); 不错且干净的解决方案!
    • 我还是坚持使用bind()函数的方案,但是确实这里展示的方式不允许以后解绑事件,如pointed out by kumkanillam。所以我想记录一个full example with both solutions
    【解决方案2】:

    每个组件都有click 处理程序,只要组件被点击就会调用它。但是在您的情况下,您想在单击 body 元素时触发组件方法,因此您的方法是要走的路。但唯一缺少的部分是bind 和清除事件。

    import Ember from 'ember';
    
    export default Ember.Component.extend({
    
      exampleProperty: 'example value',
      exampleMethodHandler: null,
    
       init() {
            this._super(...arguments);
            //bind function will return the function with scope changed to this.
            this._exampleMethodHandler = Ember.run.bind(this, this.exampleMethod);
        },
    
      didInsertElement() {    
            this._super(...arguments);
            Ember.$('body').on('click', this._exampleMethodHandler);
      },
       willDestroyElement() {
            this._super(...arguments);
            //Need to clear the registered event handler.
            if (this._exampleMethodHandler) {
                Ember.$('body').off('click', this._exampleMethodHandler);
            }
        },
      exampleMethod() {
        //Here this will refers to component,
        console.log('ComponentProperty access as usual ",this.get('exampleProperty'),event); 
      },
    });
    

    注意:我缓存了绑定函数返回的函数引用,因为我们需要提供完全相同的引用来拆除。

    所以

    Ember.$('body').on('click',this.exampleMethod.bind(this))
    Ember.$('body').off('click', this.exampleMethod);
    

    以上内容不会拆除附加的事件处理程序,因为两个引用不同。

    【讨论】:

    • 谢谢,它有效。为简单起见,this 事件参数不是必须的,以此类推didInsertElement():Ember.$('body').click(this._exampleMethodHandler ).click();,并删除方法声明中的event 参数:exampleMethod()。但是,我会坚持使用 Paul Bishop 的解决方案,因为它更简单。我正在使用willDestroyElement() 事件但没有方法处理程序,直接取消绑定方法事件:Ember.$('body').off('click', this.exampleMethod);
    • 谢谢。我更新了我的答案。我引入了缓存以进行适当的拆卸,您可以在 this twiddle 中看到 Ember.$('body').off('click', this.exampleMethod);这不会拆除事件处理程序。我们在使用绑定函数时需要注意它的陷阱。
    【解决方案3】:

    我有一个可行的解决方案,但我觉得它不是很优雅。

    Ember.js 组件必须作为事件数据传递给回调函数:

    app/components/example-component.js:

    import Ember from 'ember';
    
    export default Ember.Component.extend({
    
      exampleProperty: 'example value',
    
      didInsertElement() {
        console.log('start didInsertElement() ************************************************************');
        console.log('- "this" is the Ember.js component:');
        console.log(this.$());
        console.log('- "this.exampleProperty" has the Ember.js component value:');
        console.log(this.exampleProperty);
        Ember.$('body').click(this, this.exampleMethod).click();
        console.log('end didInsertElement() ************************************************************');
      },
      exampleMethod(event) {
        console.log('start exampleMethod() ************************************************************');
        console.log('- "this" is the DOM element that has triggered the event:');
        console.log(Ember.$(this));
        // console.log(this.$()); This doesn't work
        console.log('- "this.exampleProperty" is undefined:');
        console.log(this.exampleProperty);
        console.log('- To access the component\'s "this.exampleProperty" the event data must be used:');
        const exampleComponent = event.data;
        console.log(exampleComponent.exampleProperty);
        console.log('end exampleMethod() ************************************************************');
      },
    });
    

    这里有Ember Twiddle

    虽然这是一个可行的解决方案,但我觉得它丑陋且不实用。因此,如果您有更好的方法,请发布。

    还有一些我觉得更糟糕的选项,比如将组件属性传递给事件数据而不是整个组件,但是这样如果属性被更新,事件数据就不会得到改变。

    【讨论】:

      【解决方案4】:

      我想提供一个完整的示例,在solution of Paul Bishop 之后使用bind(),但没有unbounding problem pointed out by kumkanillam,并避免使用像exampleMethodHandler 这样的额外处理程序对象:

      app/components/example-component.js:

      import Ember from 'ember';
      
      export default Ember.Component.extend({
      
        exampleProperty: 'example value',
      
        didInsertElement() {    
          this._super(...arguments);
          // create a new function with 'this' as bound argument
          this.exampleMethod = this.exampleMethod.bind(this);
          // bind exampleMethod() function to event without arguments
          Ember.$('body').click(this, this.exampleMethod).click();
        },
        willDestroyElement() {
          this._super(...arguments);
          // unbind exampleMethod() function from event successfully without arguments
          Ember.$('body').off('click', this.exampleMethod);
        },
        exampleMethod() {
          // inside exampleMethod() 'this' is the Ember.js component 
          // as bound before, not the DOM element that has triggered the event
          console.log('Ember.js property: ", this.get('exampleProperty')); 
        },
      });
      

      bind() 函数创建一个新的绑定函数 (BF)BF 是一个 exotic function object(来自 ECMAScript 2015 的术语) 原始函数对象。调用 BF 通常会导致 执行其包装函数

      https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind

      所以如果使用bind() 创建了一个新函数,如果它被匿名使用以后就不能被重新引用。因此,这不起作用:

      // bind() creates a new function which here is anonymous
      Ember.$('body').click(this, this.exampleMethod.bind(this));
      // this.exampleMethod() is not the former anonymous function
      Ember.$('body').off('click', this.exampleMethod);
      // here things are worse as a new anonymous function is created
      Ember.$('body').off('click', this.exampleMethod.bind(this));
      

      为了以后能够引用新的绑定函数,它必须被命名:

      // create new bound function and store it in 'this.exampleMethod'
      this.exampleMethod = this.exampleMethod.bind(this);
      // bound function this.exampleMethod() referenced
      Ember.$('body').click(this, this.exampleMethod);
      // same bound function this.exampleMethod() referenced again
      Ember.$('body').off('click', this.exampleMethod);
      

      【讨论】: