@benwiley4000/bind-cache
v0.1.3
Published
Bound method cache, ideal for React
Downloads
12
Maintainers
Readme
bind-cache
Creates a cache of bound JavaScript functions created with Function.prototype.bind
.
- Only one instance is ever created for each unique set of arguments
- Designed to be allocated only for the lifetime of an associated object
- Works for binding call arguments to a method
- Ideal for React components
- 1.27 KB (unminfied)
usage
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.message = 'Hello World';
// cache is garbage-collected whenever the
// MyComponent instance is garbage-collected
this.bind = bindCache(this);
}
handleClick() {
alert(this.message);
}
render() {
// across multiple renders, only one bound instance of this.handleClick
// is ever created for this instance of MyComponent
return <MyButton onClick={this.bind(this.handleClick)}>Click Me</MyButton>;
}
}
pass arguments to bound function
// ...
handleClick(index) {
alert(this.message + ' (index: ' + index + ')');
}
render() {
return (
<div>
{this.props.dataList.map((data, i) =>
// across multiple renders, only one bound instance of
// this.handleClick is created for each member of this.props.dataList
<MyButton onClick={this.bind(this.handleClick, i)}>{data}</MyButton>
)}
</div>
);
}
}
install
With npm:
npm install @benwiley4000/bind-cache
Using a script tag:
<script src="https://unpkg.com/@benwiley4000/bind-cache"></script>
<script>
console.log(bindCache); // should be a function
</script>
dependencies
This library is written in plain ES5 JavaScript so it will run in any browser you'll need to support, but you might need to polyfill Map
and Symbol
to support older browsers.
For compatibility, pass your substitutions for Map
and Symbol
in the optional second argument to bindCache
:
this.bind = bindCache(this, { Map: MapPolyfill, Symbol: SymbolPolyfill });
For Map
you can try the es6-map
package.
For Symbol
you can try the es6-symbol
package.
However if you prefer something less heavy you can probably get away with the following:
function SymbolPolyfill() {
// this should return something that you will NEVER pass
// as an argument to bind - otherwise something could go wrong.
return 1.513543544;
}
API
bindCache(objectInstance[, options])
allocates a new cache (optionally with Map
and Symbol
fallbacks) and returns a function bind
which looks like:
bind(functionToBind[, ...callArguments])
Each call to bind(...)
returns a function bound to objectInstance
(and optionally to an additional list of call arguments).
The cache is never emptied and lasts as long as bind
is referenced somewhere in the application (after which it will be garbage-collected).
why
In some cases a JavaScript class instance needs to pass one of its member methods around as a callback argument. Generally this method sound be bound to the class instance before being passed so that when invoked it will have access to the this
context of that particular instance.
render() {
// each time render is called, we bind a copy of handleClick to the
// instance and pass it to our MyButton child as an onClick prop
return <MyButton onClick={this.handleClick.bind(this)}>Click Me</MyButton>;
}
In the world of React components in particular (and perhaps others), it's typically desirable to bind the method only once and pass that bound method many times in the render method. Creating a new bound instance on each render can cause unnecessary re-renders in child components.
Typically this approach takes one of two forms:
- Bind in the constructor
class MyComponent extends React.Component { constructor(props) { super(props); // when the instance is created, we grab our methods // from the prototype and created bound copies on // the instance this.handleClick = this.handleClick.bind(this); this.handleHover = this.handleHover.bind(this); this.handleBlur = this.handleBlur.bind(this); } // ... method definitions render() { return <MyButton onClick={this.handleClick}>Click Me</MyButton>; } }
- Use arrow function class "methods"
class MyComponent extends React.Component { // when the instance is created, we create new // arrow function members bound to our instance handleClick = () => { // ... }; handleHover = () => { // ... }; handleBlur = () => { // ... }; render() { return <MyButton onClick={this.handleClick}>Click Me</MyButton>; } }
Each of these approaches can be problematic:
- Binding in the constructor introduces a lot of boilerplate that is annoying to maintain
- Binding with arrow function members relies on the JavaScript class fields proposal which has not yet made it into the ECMAScript specification
- Binding with arrow function members also means our methods are excluded from the class prototype, which can be a problem for testing, or if you are overriding a method in a derived class (rare in the React world, but certainly possible)
- Each approach doesn't lend itself well to cases where functions need to be bound with additional call arguments. That case comes up when, for instance, an array of children needs to be rendered, each with a bound callback prop.
bindCache
avoids all of the above problems.
comparisons to prior work
cached-bind by megazazik solves the same issues, but bind-cache has a few advantages:
- Doesn't modify the object
- No need for a
key
argument - Much simpler/smaller implementation (plain ES5)
However cached-bind doesn't require Map
or Symbol
, so it can work without polyfills in older browsers.
contributing
Please feel free to open a pull request with test cases or bug fixes.