I've seen examples where methods within the LitElement class do not use .bind(this):
or this:
And I've seen code that calls .bind(this).
For example here:
or here:
Is .bind(this) neccessary for class functions? Why are some using it some not? When must we use .bind(this)?
Thanks!
I read we need to bind the Method to the Class or Object in order to have consitent/assured access to ths.
In regular ES6 classes do I need to bind in order to access this? In classes extended from LitElement do I need to bind in order to access this?
Without going into all about how this works in javascript:
Normally you want to bind when the function may be called by some other object, e.g. event handlers. If an object method calls another method in the same object, there is no disambiguation on what this is.
Function IS bound, it's being called inside an arrow function which is lexically scoped, this is the same as:
setInterval(function() {
this.updateTime();
}.bind(this), 1000);
Or alternatively in this case:
setInterval(this.updateTime.bind(this), 1000);
No clear example shown.
Item is bound in the constructor to enable calling without binding later from other events, as well as being able to remove the events later on.
HOWEVER, in this case it doesn't seem that the bindings are necessary, as the methods are being used inside lexically scoped arrow functions inside of the same scope.
Mostly same as previous item, though since the items aren't inside arrow functions but instead being passed directly, the bindings ensure they have the proper scope.
So the rule is, if the Class Method is called from the outside AND the Method uses .this, it needs to be explicily bound to the class using .bind(this)? And this is because the value of (or context) .this changes based on the calling function?
@aadamsx Yes. If the calling context of a method may not be the host element, you will want to bind that method. You can take a look at bind-decorator or autobind-decorator. I tried bind-decorator but I couldn't compile my project, probably due to tsconfig mismatch. So, I ended up writing my own decorator based on bind-decorator. Below is an example.
@customElement('sample-element' as any)
export class SampleElement extends LitElement {
private _handle = 0;
constructor() {
super();
this.addEventListener('blur', this._handleBlur);
this.addEventListener('focus', this._handleFocus);
}
@property({ type: Number })
value = 0;
connectedCallback(): void {
super.connectedCallback();
this._handle = setInterval(this._inc, 1000);
}
disconnectedCallback(): void {
clearInterval(this._handle);
}
/** No binding required. Calling context is the host element. */
private _handleFocus(): void {
this.classList.add('focused');
}
/** No binding required. Calling context is the host element. */
private _handleBlur(): void {
this.classList.remove('focused');
}
/** Binding required. Calling context is the button. */
@bind
private _reset(): void {
this.value = 0;
}
/** Binding required. Calling context is `window`. */
@bind
private _inc(): void {
this.value++;
}
protected render(): TemplateResult {
return html`
<style>
:host {
display: block;
}
:host(.focused) {
color: blue;
}
</style>
${this.value}
<button @click=${this._reset}>Reset</button>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
'sample-element': SampleElement;
}
}
@aadamsx Actually, the binding issue comes from React.
There is a detailed explanation why it is so important in React. TL;DR: sending arrow function to a React Component causes unnecessary re-rendering and using class property without binding loses this context.
However, React and lit-html have quite different approaches for this thing. React uses approach with following features which are the source of a problem:
props object.props object during deciding if it is necessary to render.These two features leads to the situation when previous handler function is not equal to the next one which means they are different, and shallow comparison will return false any time it is called.
However, lit-html uses completely different approach basing on the events. Any function goes to a @-marked attribute is just a source for addEventListener. Futhermore, to avoid adding event listeners continuously this process is higly optimized.
Thus, using inline arrow functions for event listeners doesn't cost you more than using them in array methods like map, filter or reduce. So I believe that inlining functions is the most preferrable and convenient way to work with event listeners in lit-html and lit-element.
You still can use a React way though, e.g. if you have a very big handler and you'd prefer to make code more readable. But examples you've provided that uses bind(this) just follows React way without considering the difference.
Maybe, it would be good to highlight this issue in documentation somewhere? It would be helpful for both newcomers from React and users who are not familiar with this issue at all.
@Lodin, @jolleekin, thanks for the feedback.
Yeah, coming from React, I'm a little confused.
A new PR in lit-html may just make this whole conversation a non-starter: https://github.com/Polymer/lit-html/pull/523
That's great! I hope this is fully documented in the API/Code guide.
Closing, as this has been addressed in lit-html, as noted above.
Most helpful comment
A new PR in lit-html may just make this whole conversation a non-starter: https://github.com/Polymer/lit-html/pull/523