I am confused, or I can say, I have no clue how the exporting actually works.
I have a React app, and I have some protected Routes. On login, I create a Client object instance and pass it to child components through Context, and then consume it.
Recently I saw an approach where the example code exported an object instance from a file directly and just imported it in files they wanted to consume it.
/** My approach **/
export default Example
/** The Object Instance approach **/
export default new Example()
What is the lifecycle of the object instance? Are there any disadvantages with the second approach, because it seems way easier?
If you export the class, with
export default Example
then consumers of the module will be able to instantiate their own instances, and each instance will be able to have its own data. For example
// 1.js
import TheClass from './TheClass';
const tc1 = new TheClass();
tc1.foo = 'foo';
// 2.js
import TheClass from './TheClass';
const tc2 = new TheClass();
tc2.foo = 'bar';
Both modules can continue to use tc1 and tc2 completely independently, since they're separate instances.
But if the original module exports an instance rather than a class, then all consumers of the module are forced to use the same instance:
// 1.js
import theInstance from '...';
theInstance.foo = 'foo';
// 2.js
import theInstance from '...';
// might not be a good idea to do theInstance.foo = 'bar' here
// because that will affect 1.js as well
// and will affect any other modules that imported the instance
In short - exporting the class is more reusable than exporting the instance. Sometimes potential reusability is something a script-writer will consider important, and sometimes it isn't. (And sometimes, even if you don't consider it useful initially, you may encounter a situation later that forces you to reconsider.)
And sometimes you want to make sure that there's only one instance ever in a script, in which case
export default new Example()
is a way to accomplish it.
Related
I am trying to understand the behavior of exporting class object instead of class name. Please help me make understand. Example
class Util {
method() {
return "method";
}
}
module.exports = new Util();
and then importing it like
import Util from 'Util';
I want to understand, how many times the object will be created?
Once, in the normal case. Modules are loaded and cached, and if they're imported by multiple other modules, those modules all see the same instance of the module doing the exporting.
When using CJS modules, the cache is require.cache and you can clear a module from the cache by deleting the property for it from the require.cache object, meaning that it will get loaded fresh the next time it's require'd. (I don't think it's possible to do that with ESM modules.)
Note: You're mixing CJS (module.exports =...) and ESM (import). I suggest you pick one and use it consistently, not least because you can't use require to import an ESM module.
Exporting the class will let you create new instances using new on any other modules that require/import it.
import MyModule from 'module';
// when exporting class:
const mdl = new MyModule();
console.log(mdl.constructor === MyModule); // => true
console.log(mdl.myValue); // => 'test'
console.log(MyModule.myValue); // => undefined
Exporting the instance will only export a single object that has this class as prototype. Module caching/resolution will ensure the same object is returned and the value is not re-calculated when re-importing from different modules.
import MyModule from 'module';
// when exporting instance
const mdl = new MyModule(); // TypeError: MyModule is not a constructor
console.log(MyModule.myValue); // => 'test'
First of all, the class syntax is just sugar on top of constructor functions.
If you export the Util "class", what you are exposing is that constructor function to be used with the new Util() syntax. Right?
In the other hand, if you export new Util(), instead of exporting the constructor function or the "class", you are exposing essentially an object constructed based on that constructor function's prototype object (or that "class") as it's prototype.
I'm looking a little bit deeper inside ES6 modules and I've noticed the following which I've found interesting and I'd like to clear it out.
Observation a. All modules in JavaScript are singletons by default, so
// module a.js
let notification = 10;
export default notification;
Assuming we have a module b.js and c.js, and we import there a.js notification will be shared. Notification will be readonly.
Observation b. If a function is exported from module that returns an object then a new object is created.
// module a.js
let notification = 10;
export default () => ({
notification
})
//somewhere in module b.js
import Fn from 'a.js'
let n = Fn().notification; // a new number is created since numbers are immutable in javascript but is this the reason why notification from a.js stays the same?
n = n + 10; // outputs 20
//somewhere in module c.js
import Fn from 'a.js'
let n = Fn().notification; // outputs 10
Based on my understanding, this happens because a new object is created every time?
Observation c. If we want to share the value in a module we need to follow the following pattern?
//module a.js
let notification = 10;
export default () => ({
notification,
setNotification() {
notification += 10;
}
})
If the setNotification is called in one of the modules that it's imported, then automatically then the value of notification will be 20 everywhere else the module a is imported.
Could someone shed some light on why the above is happening, and if my observations are correct?
Based on my understanding, this happens because a new object is created every time?
That's not why it happens, but yes, a new object is created every time. But the number doesn't change because you haven't changed it. You've just changed your local n variable, which is completely unconnected to the notification property of the object returned by Fn.
If we want to share the value in a module we need to follow the following pattern?
That will work, but you don't have to do it that way. This works just as well:
export default {
notification: 10
};
In another module:
import obj from "./a.js";
console.log(obj.notification); // 10
obj.notification = 20;
console.log(obj.notification); // 20
If after the code above you have a third module import and use it, they'll see 20:
import obj from "./a.js";
console.log(obj.notification); // 20
Stepping back from the mechanics of it, though, in general it's probably not best practice to modify objects you receive that way, and in fact you might even consider freezing objects you return from a module (or not returning objects other than functions at all) so you prevent odd cross-talk between the modules using your module's export.
Here's another example you may find enlightening: Although the binding you import is read-only, it's a live binding to the exporting module's local binding, which the local module can change. So this works:
source.js:
export let notification = 10;
export function setNotification(n) {
notification = n;
};
a.js:
import { notification, setNotification } from "./source.js";
console.log(notification); // 10
setNotification(20);
b.js:
import { notification, setNotification } from "./source.js";
console.log(notification); // 20
main.js:
import "./a.js";
import "./b.js";
You can think of an imported binding as a really efficient getter (accessor) for the binding in the exporting module.
Note that the order in which a.js and b.js run, and thus the values they see, is determined by the order in which main.js imported from them (and also by the fact they don't have any circular references or dynamic import in them).
I am new to react and its transpiled way of generating javascript.
In react side, I have a class Utility that uses a data object UserData organized as below -
UserDataObj.js
class UserData{
this.someobj = {};
//some function here
something(){
}
}
const UserDataObj = new UserData();
export {UserDataObj};
Utility.js
import {UserDataObj} from './data/UserDataObj';
class Utility {
doSomething(){
//UserDataObj.something();
}
}
const utility = new Utility();
export {utility};
I have another ReactApp UserApp.js, that also uses UserDataObj and Utility (although not good design wise) -
import {UserDataObj} from './data/UserDataObj';
import {utility} from './Utility';
class UserApp extends React.Component{
//does something with UserDataObj
// also does somethign with utility
}
My question is, how many utility and UserDataObj instances will be created in memory, when UserApp is rendered. My guess is, it should be only 1 instance for both. But I want to confirm if importing n times creates a new instance every time.
Any good read on this topic is greatly appreciated.
Thanks
This depends on the bundling tool, and not React. I imagine that the new browser ES Module resolution scheme works in the same way.
Most bundlers that I know of, and other import schemes such as Node.js' require module resolution will cache the import between files and always return the same exported objetcs. This is a requirement for prototype inheritance, for example, otherwise, it would mess up the instanceof operator.
That exported new Utility() instance will be the same for any module that imports it. In order to generate new instances, you would have to have a function.
As far as I understood from the module system, whenever I import 'some_module' inside a file, I will always get the same instance of that module, and not a different instance on every import.
But if that's true, I kind of don't understand this pattern I've seen in some apps:
// in a 'config_some_module.js' file
import SomeModule from 'some_module';
SomeModule.attribute = 'something';
export default SomeModule;
// in a different file;
import SomeModule from './config_some_module';
If every time I import a module I get the same instance (and not a new instance), then why is it needed to re-export that module to access it with the configuration that was done on the previous file?
Also, a second question: if that's NOT needed, how to be sure that in the second file the import will get the module when that property is already set? I assume that if both imports get you the same instance, then eventually the property will be present in SomeModule on the second file, but maybe the pattern I mentioned above is usefull because you can be sure that the changes to the module were already applied?
The reason you need to export is because otherwise, config_some_module.js would only be creating a side-effect. If you want to import from it directly, you need to export a value. If you don't export anything from config_some_module.js, you'd need to import the modified object with the side-effect by doing this:
// in 'config_some_module.js' file
import SomeModule from 'some_module';
SomeModule.attribute = 'something';
// in a different file;
import './config_some_module'; // introduce side-effect
import SomeModule from 'some_module'; // access modified object
One "gotcha" to keep in mind is that the side-effect will only occur once, no matter how many times the config_some_module.js is imported.
Lastly, the order in which you execute the import statements in the consumer does not matter as long as your usage occurs after both.
I was wondering what is the best practice to import a module's function/class in another module that the module itself needs to invoke/initialize its own function/class before being imported into another module? I don't know if I could ask my question clearly enough! So let's put it in an example.
This is my module:
// myModule.js
class MyModule {
constructor() {
// do sth
}
}
let myModule = new MyModule();
And this is how I like to import it in another module:
import MyModule from './myModule';
This actually works fine! But as you can see, in the myModule.js file I didn't export default my MyModule class because that's not the only thing that is happening in the myModule.js file! I'm also initializing the class after defining it... (I know that even if I have set my class as export default the initializing would still work fine while the module is imported somewhere else...)
So, without setting anything as exported inside of our module, or with setting the class as the export default, everything works fine when the module has been imported somewhere else... So far so good! But I'm looking for a best practice if there's one!
So here are my questions regarding such cases:
Is it ok to import a module that doesn't have anything for export?
Shall we set the class as the export default, although we are doing some more works outside of the class in the module (The initializing job that is happening after defining the class)?
Or maybe is it good to do the initializing job in another function and then export both, the class and the function, and then invoke the function to do the initializing job in the imported module?
Thanks a lot everyone! I really appreciate any helps regarding this :)
How about offering to import the class or the instance of it? Like:
// export class itself
export class MyModule {
constructor() {
// do sth
}
}
// export instance of MyModule directly
export default new MyModule();
// export a factory function if you need more work to be done
// before the instance is created
export function myModuleFactory(...args) { // define e.g. arguments to be passed to constructor
// ... do stuff
const myModule = new MyModule(...args);
// ... do more stuff
return myModule;
}
So you could do:
// import instance
import instance from './myModule';
// or class
import { MyModule } from './myModule';
// or import factory
import { myModuleFactory } from './myModule';
What to do, depeneds on what you want to accomplish with your module. If you want your app use one shared instance of a MyModule class object, you’d export and import the instance like shown above. If you want to create multiple instances in different contexts, you’d export the class itself or a factory function to return a new instance.
To keep it even more clean, you’d keep the class in yet another separate file and import it into the module providing the factory/instantiation.
Update
To answer your first question: You can import modules that don’t have any export defined. The module will be loaded and its logic executed. The thing is that as long as it won’t change global variables (like window in web development) it won’t have any effect as everything inside the module happens within an isolated scope. As you may have guessed, having modules change global vars is far from best practice.
Thanks a lot guys for your answers. I really appreciate that. Actually I got really confused on the project that I'm working on, so maybe I couldn't express what I'm trying to do...
But anyway, I write my own answers to my questions, maybe someone else find them useful:
It's always a good practice to have a export default or export for a module that we're going to write. Because every piece of code tends to have some results, right? So in a module, we should consider what we're going to achieve at the end and then export that. Export your outputs, the things that you expect your module to provide when it is going to be imported somewhere else.
If your module is a single class, it's good to export default it. Otherwise, as I said in the first answer, it all depends in what you're going to achieve in your module and what is your results. Export all the results, utility functions, etc...
You may like to do that too! But first think about your use case. As soon as the module is imported somewhere else, all the codes inside of it will be executed. So do whatever you like to do and then export the end results.