The code is as follows
class ComposerForm extends BaseForm {
constructor(formsObject, options) {
super({
...options,
setup: {},
});
this.formsObject = { ...formsObject };
}
..
}
Now i have a new form
class PreferencesForm extends ComposerForm {
constructor(company, options = {}) {
super(
{
upids: new UpidsForm(company).initialize(),
featureSettings: new FeatureSettingsForm(company)
},
options
);
}
}
When initialising the FeatureSettingsForm, i need to pass the Preference form along with the company object
Something like
{
featureSettings: new FeatureSettingsForm(company, {prefForm: this})
},
so that i can access the preference form inside featureSettings form.
But this cannot be done since this cannot be accessed inside the super method.
Any idea on how to achieve this?
If I understand you right,
You need to pass a FeatureSettingsForm instance in the object you're passing to super (ComposerForm) in the PreferencesForm constructor, and
You need this in order to create the FeatureSettingsForm instance
So you have a circular situation there, to do X you need Y but to do Y you need X.
If that summary is correct, you'll have to¹ change the ComposerForm constructor so that it allows calling it without the FeatureSettingsForm instance, and add a way to provide the FeatureSettingsForm instance later, (by assigning to a property or calling a method) once the constructor has finished, so you can access this.
¹ "...you'll have to..." Okay, technically there's a way around it where you could get this before calling the ComposerForm constructor, by falling back to ES5-level ways of creating "classes" rather than using class syntax. But it general, it's not best practice (FeatureSettingsForm may expect the instance to be fully ready) and there are downsides to have semi-initialized instances (that's why class syntax disallows this), so if you can do the refactoring above instead, that would be better. (If you want to do the ES5 thing anyway, my answer here shows an example of class compared to the near-equivalent ES5 syntax.)
Related
I have a component that has complex rendering logic.
I try to carry out this logic to helper classes, for simplifying.
To do this, in the data section (for reactivity), I create class references as follows:
export default {
data: () => ({
state: new InitialState(this),
query: new QueryController(this)
})
}
As I understand it, at this point the context of this is not yet defined.
So, I have two questions.
1) Is there a way to pass the this component context in the data section (without lifecycle hooks)?
2) Is the approach with references to external classes of vuejs philosophy contrary?
Component instance is already available when data function runs, this is one of reasons why it has been forced to be a function.
Due to how lexical this works with arrow functions, it's incorrect to use them to access dynamic this. It should be:
data() {
return {
state: new InitialState(this),
query: new QueryController(this)
};
})
The problem with InitialState(this) is that the entire component instance is passed instead of relevant data, this breaks the principle of least privilege.
Despite Vue isn't focused on OOP, there's nothing wrong with using classes. One of possible pitfalls is that classes may not play well with Vue reactivity because it puts restrictions on the implementation. Another pitfall is that classes cannot be serialized to JSON and back without additional measures, this introduces limitations to how application state can be handled.
As I understand it, at this point the context of this is not yet defined.
Only because of the way you've written the code. The component instance does exist and is available. It is sometimes used to access the values of props for determining the initial values of data properties.
For example, here is an example from the documentation:
https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
export default {
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
}
The reason why your code doesn't work is because you are using an arrow function. If you change it to the following then this will be available:
export default {
data () {
return {
state: new InitialState(this),
query: new QueryController(this)
}
}
}
See also the note here:
https://v2.vuejs.org/v2/api/#data
Note that if you use an arrow function with the data property, this won’t be the component’s instance, but you can still access the instance as the function’s first argument
As to your other question about whether using classes like this is contrary to Vue...
I don't think the use of classes like this is encouraged but they can be made to work so long as you understand the limitations. If you have a clear understanding of how Vue reactivity works, especially the rewriting of properties, then it is possible to write classes like this and for them to work fine. The key is to ensure that any properties you want to be reactive are exposed as properties of the object so Vue can rewrite them.
If you don't need reactivity on these objects then don't put them in data. You'd be better off just creating properties within the created hook instead so the reactivity system doesn't waste time trying to add reactivity to them. So long as they are properties of the instance they will still be accessible in your templates, there's nothing special about using data from that perspective.
I think computed is a better way to do what you want
export default {
computed:{
state(){
return new InitialState(this);
},
query(){
return new QueryController(this);
}
}
}
I'm not sure how to tackle this issue because there's quite a bit into it, and the behavior is one I've never seen before from JavaScript or from Vue.js
Of course, I will try to keep the code minimal to the most critical and pieces
I'm using vue-class-component(6.3.2), so my Vue(2.5.17) components look like classes :)
This particular component looks like so:
import GameInterface from '#/GameInterface';
class GameComponent extends Vue {
public gameInterface = GameInterface();
public mounted() {
this.gameInterface.launch();
}
}
GameInterface return an object with a launch method and other game variables.
In the game interface file to method looks something like this:
const GameInterface = function () {
const obj = {
gameState: {
players: {},
},
gameInitialized: false,
launch() => {
game = createMyGame(obj); // set gameInitialized to true
},
};
return obj;
}
export default GameInterface;
Great, it works, the object is passed onto my Phaser game :) and it is also returned by the method, meaning that Vue can now use this object.
At some point I have a getter method in my Vue class that looks like so:
get currentPlayer() {
if (!this.gameInterface.gameInitialized) return null;
if (!this.gameInterface.gameState.players[this.user.id]) {
return null;
}
return this.gameInterface.gameState.players[this.user.id];
}
And sure enough, null is returned even though the player and id is clearly there.
When I console.log this.user.id I get 4, and gameInterface.gameState.players returns an object with getters for players like so:
{
4: { ... },
5: { ... },
}
Alright, so it does not return the player even though the object and key are being passed correctly...
But I found an extremely strange way to "FIX" this issue: By adding JSON.parse(JSON.stringify(gameState)) like so
get currentPlayer() {
// ...
if (!this.gameInterface.gameState.players[this.user.id]) {
// add this line
JSON.stringify(this.gameInterface.gameState);
return null;
}
return this.gameInterface.gameState.players[this.user.id];
}
It successfully returns the current player for us... Strange no?
My guess is that when we do this, we "bump" the object, Vue notices some change because of this and updates the object correctly. Does anyone know what I'm missing here?
After working on the problem with a friend, I found the underlying issue being a JavaScript-specific one involving Vue's reactive nature.
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
In this section of the documentation, a caveat of Vue's change detection is discussed:
Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive.
When, in my game run-time, I set players like so:
gameObj.gameState.players[user.id] = {...playerData}
I am adding a new property that Vue has not converted on initialization, and Vue does not detect this change. This is a simple concept I failed to take into account when developing my game run-time.
In order to correctly set a new player, I've decided to use the spread operator to change the entirety of the players object, which Vue is reacting to, and in turn, Vue will detect my player being added like so:
gameObj.gameState.players = {
...gameObj.gameState.players,
[user.id]: {...playerData}
}
Vue also discusses another method called $set, which you can read on the same page.
Can I write React lifecycle methods as class properties?
I've been using class properties for a while as I like the fact that I no longer have to manually bind my methods, but I'd like to keep some consistency across my components and I'm wondering if there is any drawback on writing the React lifecycle methods as class properties
import React, { Component } from 'react';
class MyComponent extends Component {
render = () => {
return (
<div>Foo Bar</div>
);
}
}
export default MyComponent;
For example, is the context of this class property affected compared to the context in an equivalent method. Given that the render method in the above code is written as an arrow function, this concern seems relevant.
In a way, the true answer depends on your build pipeline and what the resulting Javascript output looks like. There are two primary possibilities:
Input Code
Let's start by saying you are writing the following before going through any sort of pipeline transformations (babel, typescript, etc):
class Test {
test = () => { console.log('test'); };
}
Output as class member variable.
In one possible world, your pipeline will also be outputting the test function as a member variable for the output class. In this case the output might look something like:
function Test() {
this.test = function() { console.log('test'); };
}
This means that whenever you write new Test() the test function is going to be recreated every single time.
Output as class prototype function
In the other major possibility, your pipeline could be recognizing this as a function property and escape it from the class instance to the prototype. In this case the output might look something like:
function Test() {
}
Test.prototype = {
test: function() { console.log('test'); }
}
This means that no matter how many times you call new Test() there will still be only one creation of the test function around in memory.
Desired behavior
Hopefully it's clear that you want your end result to have the function end up on the prototype object rather than being recreated on each class instance.
However, while you would want the function to not end up as a property, that doesn't necessarily mean you couldn't write it that way in your own code. As long as your build chain is making the correct transformations, you can write it any way you prefer.
Although, looking at the default babel settings (which your babeljs tag leads me to believe you are using) it does not make this transformation for you. You can see this in action here. On the left I've created one class with the function as a property and one class with the function as a class method. On the right hand side, where babel shows it's output, you can see that the class with the function as a property still has it being an instance-level property, meaning it will be recreated each time that class's constructor is called.
I did find this babel plugin, which seems like it might add this transformation in, but I've not used it for myself so I'm not positive.
In my experience, the most reason for writing a method as a class property is when the method will be passed as a callback, and you need it to always be bound to the instance. React lifecycle methods will always be called as a method, so there's no reason to bind them (and you incur a tiny memory penalty when you do). Where this makes a difference is when you're passing a function to a component as a callback (e.g. onClick or onChange).
Take this example:
class BrokenFoo extends React.Component {
handleClick() {
alert(this.props.message);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
)
}
}
The function represented by this.handleClick is not automatically bound to the component instance, so when the method tries to read the value of this.props it will throw a TypeError because this is not defined. Read this article if you're not familiar with this; the problem described in section 4.2 "Pitfall: extracting methods improperly" is essentially what's happening when you pass around a method without making sure it's bound correctly.
Here's the class, rewritten with the handler as a class property:
class HappyFoo extends React.Component {
handleClick = () => {
alert(this.props.message);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
)
}
}
Effectively, you can think of the handleClick definition in the second example as placing this code into the component's constructor (which is just about exactly the way Babel does it):
this.handleClick = () => {
alert(this.props.message);
}
This achieves the same thing as calling bind on the function (as described in the linked article) but does it a little differently. Because this function is defined in the constructor, the value of this in this.props.message is bound to the containing instance. What this means is that the function is now independent of the calling context; you can pass it around and it won't break.
The rule of thumb that I follow: by default, write methods as methods. This attaches the method to the prototype and will usually behave the way you'd expect. However, if the method is ever written without parentheses (i.e. you're passing the value and not calling it), then you likely want to make it a class property.
I have a container that I need to change the UI form showing the form or showing a success page.
The container has a state.showSuccess and I need the MyFormModule to be able to call the container to change the state.
The below code works but I'm getting the following warning:
JSX props should not use .bind()
How can I get this to work without using .bind()?
...
const myPage = class extends React.Component {
state = { showSuccess: false };
showSuccess() {
this.setState({
showSuccess: true,
});
}
render() {
const { showSuccess } = this.state;
if (showSuccess) {...}
....
<MyFormModule showSuccess={this.showSuccess.bind(this)} />
You should first understand WHY this is a bad practice.
The main reason here, is that .bind is returning a new function reference.
This will happen on each render call, which may lead to a performance hit.
You got 2 options:
Use the constructor to bind your handlers (this will run only once).
constructor(props) {
super(props);
this.showSuccess = this.showSuccess.bind(this);
}
Or create your handlers with arrow functions so they will use the
lexical context for this, hence you won't need to bind them at
all (you will need a babel plugin):
showSuccess = () => {
this.setState({
showSuccess: true,
});
}
You should not use this pattern (as others suggested):
showSuccess={() => this.showSuccess()}
Because this will as well create a new function on each render.
So you may bypass the warning but you are still writing your code in a bad practice design.
From the ESLint docs:
A bind call or arrow function in a JSX prop will create a brand new
function on every single render. This is bad for performance, as it
will result in the garbage collector being invoked way more than is
necessary. It may also cause unnecessary re-renders if a brand new
function is passed as a prop to a component that uses reference
equality check on the prop to determine if it should update.
Use an arrow function when defining showSuccess
showSuccess = () => {
this.setState({
showSuccess: true,
});
}
Use an arrow function since they automatically inherit the this context of wherever they are defined.
showSuccess={() => this.showSuccess()}
Here is a link to the facebook documentation on this subject, which lists this method among others as a solution. Interestingly, they also list using .bind in the prop as one of the solutions, even though it produces a warning when actually used.
From that documentation, you'll note that this is a potential performance issue, since the function will be recreated on every render:
Note:
Using an arrow function in render creates a new function each time the
component renders, which may have performance implications (see
below).
But also from the same link:
Is it OK to use arrow functions in render methods? Generally speaking,
yes, it is OK, and it is often the easiest way to pass parameters to
callback functions.
If you do have performance issues, by all means, optimize!
So I would say if your component will be re-rendering very frequently, you should use one of the other solutions: bind in the constructor, or define the method with an arrow function in the first place. But if not, use whatever method seems cleanest to you.
I recently learned that all node modules are cached and behave similar to singletons in most instances.
The problem I am trying to solve is to not have every import result in the same instance being returned. This is probably very simple to figure out however I'm having trouble landing on a solid design pattern as I'm new to Node and ES6.
The goals I'm trying to achieve are:
Private fields
Consumers of the imported module can new up instances
instanceof comparison
The best I was able to come up with is the following:
export default () => {
let _foo = 'bar';
return new class {
get foo() {
return _foo;
}
set foo(value) {
_foo = value;
}
};
};
However this doesn't quite meet all the goals I'm trying to achieve.
Using this method importing modules can't use instanceof to compare prototypes.
It also doesn't matter if importers use the new keyword when creating an instance. Calling let instance = new Module() and let instance = Module() result in the same thing.
I tried to get around this by removing the new keyword from the functions return however this resulted in the importer having to do the following to get a new instance: new (Module())
I have also tried exporting constructor functions but this resulted in the loss of private fields.
What is the proper way to export a constructor function/class from a node module?
UPDATE:
After playing around some more I was able to come up with the following:
const _sFoo = Symbol();
export default class {
constructor() {
this[_sFoo] = 'default';
}
get foo() {
return this[_sFoo];
}
set foo(value) {
this[_sFoo] = value;
}
}
This seems to meet all of my goals however I'm still not sure if this is the best design pattern...
The problem I am trying to solve is to not have every import result in the same instance being returned. This is probably very simple to figure out however I'm having trouble landing on a solid design pattern as I'm new to Node and ES6.
You have a couple options:
You can export the constructor and let the code that is loading your module call that constructor to create their own object. This allows the calling code to create as many independent objects as they desired. Exporting a constructor would require new to be used by the caller unless the constructor explicitly detects they were called without new and then adapts to still return a new instance.
You can export a factory function and let the code that is loading your module call that factory function to create as many of their own objects as they want. The factory function would be just called as a normal function and it would return a new object each time it was called.
You can export a method that, when called, does whatever you want including creating the desired object and returning it (perhaps embedded in an object of other things too). This is just a variant of the factory function, but may include a bunch of things at once.
The goals I'm trying to achieve are:
Private fields
The above do not help you at all with private fields per object. That is a completely separate discussion.
Consumers of the imported module can new up instances
Option 1 above allows the caller to use new directly. The other options are factory functions so they would not use new.
instanceof comparison
You have to export the constructor directly (option 1 above) in order to use instanceof with it. The other options don't export the constructor so you don't have anything to use instanceof with.
What is the proper way to export a constructor function/class from a node module?
You just export the constructor. In Javascript, constructors are just functions so you just export the constructor and then the caller can use let x = new SomeConstructor() to create their own object. They can likewise use if (x instanceof SomeConstructor). With ES6 syntax, you just export the class name and that's equivalent to exporting the constructor.