GitXplorerGitXplorer
l

reflective-bind

public
0 stars
6 forks
0 issues

Commits

List of commits on branch master.
Unverified
bd7b00fbc9012165b9cfa2847f099ad4eb8582b3

v0.0.3-rc2

ddounan committed 7 years ago
Unverified
cdad4e47498032502c133f1cd312d6747beeceae

Update CHANGELOG for 0.0.3

ddounan committed 7 years ago
Unverified
d3c052fd4e4cd34a8a9cb3d23244f6a0bd60c7d8

v0.0.3-rc1

ddounan committed 7 years ago
Unverified
49d7b9a1b0e4704e1531a5a8b9ebe29a950e20e9

Add more nested arrow function tests (#5)

ddounan committed 7 years ago
Unverified
f32631d2686b44ee961bb6d525ae2134da81b82b

Support referencing non-constant vars (#4)

ddounan committed 7 years ago
Unverified
2f507300ee14a5c4282f6c207cd56d36cc07aa58

Update README

ddounan committed 7 years ago

README

The README file for this repository.

Build Status codecov

Reflective Bind

In React, using inline functions (arrow functions and Function.prototype.bind) in render will cause pure components to wastefully re-render. As a result, many React developers encourage you to never use inline functions in render. However, others think that avoiding them is premature optimization.

With reflective-bind, you can freely use inline functions in render without worrying about wasteful re-rendering of pure components.

The best part is, it requires almost no code change 🙌

Installation

npm install --save reflective-bind

Using the babel plugin

Add it to the top of your plugin list in .babelrc (it must be run before other plugins that transform arrow functions and bind calls):

"plugins": [
  "reflective-bind/babel",
  ...
]

And call reflective bind’s shouldComponentUpdate helper function in your component:

import {shouldComponentUpdate} from "reflective-bind";

class MyComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return shouldComponentUpdate(this, nextProps, nextState);
  }
  ...
}

If you’re already using React.PureComponent and want to avoid updating all of your components, consider monkey patching shouldComponentUpdate 🙊

import React from "react";
import {shouldComponentUpdate} from "reflective-bind";

React.PureComponent.prototype.shouldComponentUpdate = function(
  nextProps,
  nextState
) {
  return shouldComponentUpdate(this, nextProps, nextState);
};

If you do not want the babel plugin to process a specific file, add the following line to your file:

// @no-reflective-bind-babel

Dependencies

The babel plugin will add ES6 import declarations to your code. This shouldn’t be an issue if you’re using using babel-preset-env or babel-preset-es2015, but just make sure that some plugin/preset can transform the import declarations to your needs.

What the plugin does

The plugin simply transforms inline functions into calls to reflectiveBind. This then allows the shouldComponentUpdate helper function to use reflectiveEqual in the shallow comparison equality check.

Using reflectiveBind manually

Binding your function with reflectiveBind simply stores the original function, the context (thisArg), and the args on the bound function instance. This allows you to check if two reflectively bound functions are equal.

import reflectiveBind, {reflectiveEqual} from "reflective-bind";

function baseFn(msg) {
  alert(msg);
}

const fn1 = reflectiveBind(baseFn, undefined, "hello");
const fn2 = reflectiveBind(baseFn, undefined, "hello");

fn1 === fn2 // false
reflectiveEqual(fn1, fn2) // true

const fn3 = reflectiveBind(baseFn, undefined, "world");
reflectiveEqual(fn1, fn3) // false

Note that reflectiveEqual only works for reflectively bound functions.

reflectiveEqual(1, 1) // false
reflectiveEqual(baseFn, baseFn) // false

We also expose a isReflective helper function that lets you check if something is a reflectively bound function.

Flow types

All exported functions are flow typed out of the box. reflectiveBind is typed with function overloading:

// Function with 0 args
declare function reflectiveBind<A>(f: () => A, ctx: mixed): () => A;

// Function with 1 arg
declare function reflectiveBind<A, B>(f: (A) => B, ctx: mixed): A => B;

declare function reflectiveBind<A, B>(f: (A) => B, ctx: mixed, a: A): () => B;

...

We currently support reflectiveBind calls up to 4 args:

reflectiveBind(baseFn, ctx, a, b, c, d);

Babel plugin examples

The following are examples of some inline functions that will be transformed into calls to reflectiveBind by the babel plugin:

  • Inline arrow functions:
function MyComponent(props) {
  const msg = "Hello " + props.user.name.first;
  return <PureChild onClick={() => alert(msg)} />
}
  • Function.prototype.bind:
function MyComponent(props) {
  const handleClick = props.callback.bind(undefined, "yay");
  return <PureChild onClick={handleClick} />
}
  • Multiple assignments / reassignments:
function MyComponent(props) {
  let handleClick = () => {...};
  
  if (...) {
      handleClick = () => {...};
  } else if (...) {
      handleClick = () => {...};
  }

  return <PureChild onClick={handleClick} />
}
  • Ternary expressions:
function MyComponent(props) {
  const handleClick = props.condition
    ? () => {...}
    : () => {...};

  return <PureChild onClick={handleClick} />
}
  • For maximum optimization, avoid accessing nested attributes in your arrow function. Prefer to pull the nested value out to a const and close over it in your arrow function.
function MyComponent(props) {
  
  // PureChild will re-render whenever `props` changes (bad)
  const badHandleClick = () =>  alert(props.user.name.first);
  
  const firstName = props.user.name.first;
  // Now, PureChild will only re-render when firstName changes (good)
  const goodHandleClick = () => alert(firstName);
  
  return (
    <div>
      <PureChild onClick={badHandleClick} />
      <PureChild onClick={goodHandleClick} />
    </div>
  );
}

Unsupported cases

There are a few edge cases that can cause an arrow function to not be transformed. Nothing breaks, you just won’t have optimized code.

  • Your arrow function should not close over variables whose value is set after the arrow function.
function MyComponent(props) {
  let foo = 1;
  
  const badHandleClick = () => {
    // Referencing `foo`, which is reassigned after this arrow function, will
    // prevent this arrow function from being transformed.
    alert(foo);
  };
  
  foo = 2;

  return <PureChild onClick={badHandleClick} />
}
  • Your arrow function must be defined inline the JSX, or at most 1 reference away.
function MyComponent(props) {
  // This arrow function won't be transformed because `fn` is not referenced
  // directly in the JSX.
  const fn = () => {...};
  const badHandleClick = fn;
                    
  // This arrow function will be transformed since `goodHandleClick` is
  // referenced directly in the JSX.
  const goodHandleClick = () => {...};
                    
  return (
    <div>
      <PureChild onClick={badHandleClick} />
      
      <PureChild onClick={goodHandleClick} />
      
      {/* This will be optimized since it is defined directly in the JSX */}
      <PureChild onClick={() => {...}} />
    </div>
  );
}