Does anybody has experience in working with React components as plain JS objects instead of annoying ES6 classes and deprecated .createClass method.
Maybe you have some examples of factory functions or similar to share?
Thanks!
React.Component is a plain javascript function, since es6 classes are syntactic sugar around them. So we could use whatever es5 class-like concept we like e.g. I just borrowed Backbone's extend method here:
// From backbone
var extend = function(protoProps) {
var parent = this;
var child;
var extendObj = function(obj1, obj2) {
for (var i in obj1) {
if (obj1.hasOwnProperty(i)) {
obj2[i] = obj1[i];
}
}
};
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent constructor.
if (protoProps && hasOwnProperty.call(protoProps, 'constructor')) {
child = protoProps.constructor;
} else {
child = function() { return parent.apply(this, arguments); };
}
// Set the prototype chain to inherit from `parent`, without calling
// `parent` constructor function.
var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;
// Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) extendObj(child.prototype, protoProps);
// Set a convenience property in case the parent's prototype is needed
// later.
child.__super__ = parent.prototype;
return child;
};
React.Component.extend = extend;
Then we could create components like this:
var MyComponent = React.Component.extend({
constructor: function() {
console.log('hello from react component');
this.state = {
open: false
};
React.Component.apply(this, arguments);
}
});
new MyComponent();
That's just an example (and untested), you could do any kind of prototypal implementation you like since it's just a normal function. If you search for "es5 inheritance" you should be able to apply any of those solutions.
I think my answer is late. But I do make a lot of React Components using traditional prototype based javascript objects. If you love prototype based object, you can try the following :)
A simple example:
step 1: install inherits module
npm install inherits -S
then,
const React = require('react'); // switch to import, if you like
const is = require('prop-types');
const inherits = require('inherits');
inherits(MyComponent, React.Component);
module.exports = MyComponent;
var prototype = MyComponent.prototype;
MyComponent.defaultProps = {
onClick: function(){ }
};
MyComponent.propTypes = {
onClick: is.func,
href: is.string,
label: is.string
}
function MyComponent(props) {
React.Component.call(this, props);
this.state = {clicked: false};
}
prototype.render = function() {
return (
<a href={this.props.href} onClick={this.props.onClick}>
{this.props.label}
</a>)
}
// for debugging purpose, set NODE_ENV production, will remove the following
if (process.env.NODE_ENV !== 'production') {
MyComponent.displayName = 'MyComponent';
}
A more advanced way to separate your concerns is to put your methods in different files. ( Usually, the protected, or private methods, something you do not need to know after couple months or years.) Then, merge them onto the prototype object. You can do it in the following way.
...
const _proto = require('./prototype'); //make a prototype folder, and merge all files' methods into one.
...
var prototype = Object.assign(MyComponent.prototype, _proto);
Or, you want to make your component as an EventEmitter, you can do it like following:
....
const _proto = require('./prototype');
const Emitter = require('component-emitter');
....
var prototype = Object.assign(MyComponent.prototype, _proto, Emitter.prototype);
function MyComponent(props) {
React.Component.call(this, props);
this.onClick = _=> this.emit("click");
}
prototype.render = function() {
return <a href={this.props.href} onClick={this.onClick}>{this.props.label}</a>
}
In the prototype folder, you can write like following:
index.js
Object.assign(exports, require('./styles.js').prototype)
styles.js
const prototype = exports.prototype = {};
prototype.prepareStyles = function() {
var styles = Object.defineProperties({}, {
wrapper: { get: _=> ({
backgroundColor: '#333'
})},
inner: {get: _=> {
return this.state.clicked ? {...} : {...}
}}
});
Object.defineProperties(this, {
styles: {get: _=> styles}
})
}
//All the methods are prefixed by prototype, so it is easy to cut and paste the methods around different files, when you want to hide some methods or move some methods to be with the constructor to make your component more easy to read.
then, in the main file. simply call the method to prepare all the styles:
function MyComponent(props) {
React.Component.call(this, props);
this.prepareStyles();
}
and use the styles,
prototype.render = function() {
return (
<div style={this.styles.wrapper}>
<div styles={this.styles.inner}>hello world</div>
</div>
)
}
Related
I have a typescript singleton class like so:
export default class MySingleton {
private constructor({
prop1,
prop2,
...
}: MySingletonConfig) {
this.prop1 = prop1 ?? 'defaultProp1';
this.prop2 = prop2;
this.prop3 = prop3 ?? 'defaultProp3';
/* ... some instruction ... */
MySingleton.instance = this;
}
static getInstance(params?: Configuration): MySingleton {
if (!this.instance && !params) {
throw MySingleton.instantiationError;
}
if (!this.instance) {
new MySingleton(params);
return this.instance;
}
return this.instance;
}
}
When I want to unit test it using jest, like so:
describe('getInstance()', () => {
test('it should return the same instance every time', () => {
const params = {
/* ... all the params ... */
};
const mySingleton = MySingleton.getInstance(params);
expect(MySingleton.getInstance()).toEqual(mySingleton);
});
test('it should return the instance with the default value', () => {
const params = {
/* ... ONLY THE REQUIRED PARAMS ... */
};
const mySingleton = MySingleton.getInstance(params);
expect(mySingleton.prop1).toEqual('defaultProp1');
expect(mySingleton.prop3).toEqual('defaultProp3');
});
});
This is failing, because we share the same instance between the 2 tests (as the singleton pattern work), therefore the second instantiation is useless.
Is there a way to reset/destroy the previous instantiation in order to properly check if those default values are properly setted with the second intantiation?
I don't see why you couldn't do:
MySingleton.instance = null;
const mySingleton = MySingleton.getInstance(params);
Ideally the istance property should be really private, but nobody prevents you from adding a reset() method on your class.
It is not particularly neat, as it would basically be for testing purposes only, but at least it would be more close to the canonical implementation of the singleton pattern.
That being said, I would carefully consider if using a singleton is a good idea. It might create a lot of headaches when unit testing your code.
Basically, the same problem you have here could present itself elsewhere when you try to test some code that makes use of your singleton.
I have a code where a function is used to modify the existing functions and return a new function reference. I want that function to be applied to specific methods of a class.
My current code is
function modifyMethod(func) {
return function() {
console.log('working');
return func.apply(this, arguments);
};
}
function modifyClassMethods(ClassName, methodArray) {
// The code goes here
return ClassName;
}
class Temp {
hi() {
console.log("hi method");
}
}
Temp = modifyClassMethods(Temp, ["hi"]);
const temp = new Temp();
// This should print
//
// working
// hi method
temp.hi();
When I try to call the method modifyMethod with Temp.hi, func is undefined. If I create an object and then modify the method, then the new method will be applied only to the method of that particular object and not to all the objects of that particular class.
Note that this is just an example. I want to apply this modification to the methods of multiple classes. So, I can't generalize the method names also. Any code snippet for the modifyClassMethods will be helpful.
The methods defined with method syntax in the body of a class construct that aren't marked static are prototype methods and so they're on Temp.prototype, not Temp itself. So that's where you'd update them:
Temp.prototype.hi = modifyMethod(Temp.prototype.hi);
Only static methods end up on Temp itself.
You may see other functions created within the class body using the class fields proposal's syntax:
class Temp {
hi = () => {
//
};
}
Those are instance methods. They're created by the constructor, and re-created for each instance, roughly as though they'd been written like this:¹
class Temp {
constructor() {
this.hi = () => {
//
};
}
}
You can't wrap those until/unless an instance is created, as they're instance-specific.
So to wrap up, consider:
class Temp {
static staticMethod() {
// ...
}
prototypeMethod() {
// ...
}
instanceMethod = () => {
// ...
};
constructor() {
this.anotherInstanceMethod = () => {
// ...
};
this.yetAnotherInstanceMethod = function {
// ...
};
}
}
That class shows the three types of methods:
Static Methods, such as staticMethod, which you'll find on Temp (e.g., Temp.staticMethod);
Prototype Methods, such as prototypeMethod, which you'll find on Temp.prototype (e.g., Temp.prototype.prototypeMethod); and
Instance Methods, such as instanceMethod, anotherInstanceMethod, and yetAnotherInstanceMethod, which you'll find on the instances themselves, if/when any instances are created
¹ Technically, they're created as though with Object.defineProperty like this:
class Temp {
constructor() {
Object.defineProperty(this, "hi", {
value: () => {
//
},
writable: true,
configurable: true,
enumerable: true
});
}
}
...not via simple assignment. I used simple assignment in the example to keep it...simple. :-)
With ES5 constructor and prototype approach I can add public (prototype) properties as below:
function Utils(){}
Utils.prototype.data = {};
var utils = new Utils();
console.log(utils.data); //{}
The ES6 class allows me to define only public methods in the class. I build an app with a class-approach and I don't want to mix constructors and classes features. The working code that I figured out is:
class Utils(){
get _data(){
const proto = Object.getPrototypeOf(this);
if(!proto._status) proto._data = {};
return proto._data;
}
}
const utils = new Utils();
console.log(utils._data); //{}
When I call _data getter method, it checkes whether the _data property exists in the prototype object. If so, it returns it, otherwise it initiates the _data property.
Is it a good practice? Is there any other way to do it better?
To make data a public instance property:
class Utils {
constructor () {
this.data = {}
}
}
To make data a public static property, get/set is probably the best way:
let data = {}
class Utils {
get _data () {
return data
}
set _data (d) {
data = d
}
}
I don't know if the code you provided is your full code or not, but when I run it, it throws an error:
class Utils {
get _data(){
const proto = Object.getPrototypeOf(this);
if(!proto._status) proto._data = {};
return proto._data;
}
}
/* TEST */
const a = new Utils();
a._data.ok = 'ok';
const b = new Utils();
console.log(b._data.ok);
If I understand you correctly, you want all instances of Utils to share the same data property.
There is a few ways that I can think of to do what you need, but it might "mix constructors and classes features" (I don't really get what you mean by that).
1: Good ol' ES5 way
class Utils {}
Utils.prototype.data = {};
/* TEST */
const a = new Utils();
a.data.ok = 'ok';
const b = new Utils();
console.log(b.data.ok);
2: Same as your way, but in it's constructor
class Utils {
constructor(){
if (!this.data) {
Utils.prototype.data = {};
}
}
}
/* TEST */
const a = new Utils();
a.data.ok = 'ok';
const b = new Utils();
console.log(b.data.ok);
Though, as the data property needs to be shared across instances, I'd suggest you to add the property to its prototype using Object.defineProperty method to make it unwritable and unconfigurable:
Object.defineProperty(Utils.prototype, 'data', {
value: {},
writable: false,
enumerable: true,
configurable: false
});
This is to ensure the data property cannot be reassigned or deleted, thus minimising the chance of mistakenly reset the data or etc.
I'd recommend the first way (with Object.defineProperty) because it is :
More foolproof
Clearer
Easier to maintain
I want to wrap all of jQuery's DOM functions for a js class, but have those functions only deal with a single property of that class.
class Foo {
constructor() {
this.$wrapper = $('<div>wrapper</div>')
}
}
var f = new Foo();
// have to call this
f.$wrapper.appendTo($('body'));
// want to call this
f.appendTo($('body'));
I could do this manually, which would take eons, by creating a same-named method for each jQuery method and simply always apply it to this.$wrapper. But I wonder if there is a simpler way to do this.
https://jsfiddle.net/gy9tLyk2/
Yes, just extend jQuery.fn.init and use a super call:
class Foo extends jQuery.fn.init {
constructor() {
super('<div>wrapper</div>');
}
}
Foo.prototype.constructor = jQuery;
new Foo().appendTo('body');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Note I replace Foo.prototype.constructor because jQuery will access the constructor property of the instance and call it. But ES6 classes like Foo are not callable, and jQuery is not supposed to call Foo.
Not sure if babel will be able to transpile correctly, here is the ES5 code:
function Foo() {
this.init('<div>wrapper</div>');
}
Foo.prototype = Object.create(jQuery.fn);
new Foo().appendTo('body');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
One option is using ES2015 proxies, i.e. the Proxy constructor.
class Foo {
constructor() {
this.$wrapper = $('<div>wrapper</div>');
return new Proxy(this, {
get: function(target, prop, receiver) {
if ( target.hasOwnProperty(prop) ) {
return target[prop];
}
return function() {
let ret = target.$wrapper[prop](...arguments);
if ( ret !== target.$wrapper ) return ret;
return receiver;
}
}
});
}
}
var f = new Foo();
var a = f.appendTo($('body')).text('changed').prop('tagName');
But it's not recommended for several reasons:
Browser support
Proxies are slow
jQuery has getters and setters and creating a proxy handler than handles all the jQuery and the class methods can be tricky.
Given class
class Test {
test() {
console.log('test called');
}
}
And some object toExtend = {}
How can I extend this object so it will have test method?
Object.assign ( as well as _.extend, _.assign, $.extend) do not copy methods. What is preferable way to do that?
Note that toExtend is passed from outside
UPD:
toExtend is instance of another class and has it's own prototype's methods
Object Literals
For object literals, which start with no protoype of their own (Object.getPrototypeOf(toExtend) === Object.protoype)), you can simply use Object.setPrototypeOf to extend the object:
class Test {
test() {
console.log('test called');
}
}
const toExtend = {};
// Set the prototype, so you "inherit" methods:
Object.setPrototypeOf(toExtend, Test.prototype);
toExtend.test();
In older runtimes, you would have to manually assign the prototype:
function Test() {
// noop ctor
}
Test.prototype.test = function() {
console.log('test called');
};
var toExtend = {};
// Set the prototype, so you "inherit" methods:
toExtend.__proto__ = Test.prototype;
toExtend.test();
Class Instances
For instances of an existing class, things are significantly more complex. They do have a prototype of their own, potentially with properties that must be copied, so you need to walk through those:
class Foo {
test() {
console.log('test');
}
}
class Bar {
toast() {
console.log('toast');
}
}
function dynamicExtend(target, base) {
const baseProto = Object.getPrototypeOf(target);
if (baseProto == Object.prototype) {
// simple case: no existing prototype
Object.setPrototypeOf(target, base.prototype);
} else {
// complex case: existing prototype
const proxyClass = class extends base {};
const proxyProto = proxyClass.prototype;
// assign the target properties
Object.getOwnPropertyNames(baseProto).forEach(n => {
const desc = Object.getOwnPropertyDescriptor(baseProto, n);
Object.defineProperty(proxyProto, n, desc);
});
Object.setPrototypeOf(target, proxyProto);
}
}
const targets = [{},
new Bar()
];
targets.forEach(t => {
dynamicExtend(t, Foo);
t.test();
if (t.toast) {
t.toast();
}
});
Note that, thanks to the proxy class, this does break instanceof style inheritance checks.
__proto__
As #PatrickRoberts noted in the comments, __proto__ is deprecated, so you should prefer setPrototypeOf whenever possible.