Short practical guide to clarify the use cases of Decorators (aka HOF) and High Order Components. The examples is applicable for general JS, but are contructed using ES2015 + React.
Higher Order Functions (HOF) are functions that receive functions as arguments (Inception :/) and return functions. They operate over functions instead of "simple" variables. Decorators provide syntactic suggar for implementing HOFs JS, to modify and extend passed functions.
Higher Order Components (HOC) are a React specific patter, very much similar to the aforemntioned HOF. Instead of receiving functions as argumnets, Components are passed. They are especially useful to add stateful wrappers to componets and alter livecycle methods.
Short examples of different use cases for Decorators
and HOC
.
They can be added with @
.
Modify the properties of certain object's property.
const readonly = (target, name, descriptor) => {
descriptor.writable = false;
return descriptor;
}
class Person {
@readonly
name = 'Alain';
}
const me = new Person();
console.log('Person.name => ', me.name);
// My name is: Alain
me.name = 'John';
// Uncaught TypeError: Cannot assign to read only property 'name' of object '#<Person>'
Modify the calls of certain methods. Provided examples:
- Override the methods arguments
const overrideMethodArguments = (target, name, descriptor) => {
const original = descriptor.value;
descriptor.value = (...args) => {
console.log(`Calling ${name} with arguments: ${JSON.stringify(args)}`);
return original.apply(this, ['OVERRIDE ARG']);
}
}
class Human {
@overrideMethodArguments
getName (name) {
return name;
}
};
const h = new Human();
console.log(`Decorated Human.getName() => ${h.getName('myName', 'arg2')}`);
// Decorated Human.getName() => OVERRIDE ARG
- Add execution time log (visible in browser console)
const logExecutionTime = (target, name, descriptor) => {
const oldValue = descriptor.value;
descriptor.value = (...args) => {
console.time(`timeProfiling-${name}`);
const result = oldValue.apply(this, [...args]);
console.timeEnd(`timeProfiling-${name}`);
// timeProfiling-foo: 0.00390625ms
return result;
}
};
class Dog {
@logExecutionTime
foo () {
console.log('bark bark');
}
};
const d = new Dog();
d.foo();
// timeProfiling-foo: 0.257080078125ms
// bark bark
Decorate ES2015 classes.
console.log('\n3- CLASS DECORATORS');
const superhero = target => {
target.power = 'A superHero can fly'
target.terminate = enemy => `${enemy} has been terminated`
};
@superhero
class SuperHero {};
console.log(SuperHero.power);
// A superhero can fly
console.log(SuperHero.terminate('Venom'));
// Venom has been terminated
Simple component to change
const Component = ({text = 'default value', otherProp}) => (
<div>
<div style={{ color: 'red'}}>text: {text}</div>
<div style={{ color: 'blue'}}>otherProp: {otherProp}</div>
</div>
);
ReactDOM.render(<Component text={'dummy text'} />, document.getElementById('app'));
const AddPropHOC = Component => {
const Wrapping = props => (
<Component {...props} otherProp={'addedProp'} />
)
return Wrapping;
}
const NewPropToComponent = AddPropHOC(Component);
ReactDOM.render(<NewPropToComponent text={'dummy text'} />, document.getElementById('app'));
const ModifyPropHOC = (propName, newValue) => Component => {
const Wrapping = props => {
const modified = Object.assign({}, props);
modified[propName] = newValue;
return <Component {...modified} />;
}
Wrapping.propTypes = {
text: React.PropTypes.string,
}
return Wrapping;
}
const ModifyPropInComponent = ModifyPropHOC('text','newValue')(Component);
ReactDOM.render(<ModifyPropInComponent text={'dummy text'} />, document.getElementById('app'));
const RemovePropHOC = propName => Component => {
const Wrapping = props => {
let modified = Object.assign({}, props);
delete modified[propName];
return <Component {...modified} />
}
return Wrapping;
}
const RemovePropInComponent = RemovePropHOC('text')(Component);
ReactDOM.render(<RemovePropInComponent text={'dummy text'} />, document.getElementById('app'));