React Turbo (beta)

npm npm bundle size npm type definitions GitHub

The ultimate solution for React performance optimization

react-turbo transforms your React apps to have truly fine-grained reactivity, so you don’t need to worry about React performance optimization anymore. Read this article to know more.

Installatoin

react-turbo comes with a babel plugin, so the prerequisite is react-app-rewired. If you’ve installed it, you can skip and jump to install react-turbo.

Install react-app-rewired and customize-cra

Follow the steps of react-app-rewired and customize-cra.

npm i --save-dev react-app-rewired customize-cra

Modify package.json, add config-overrides.js and .babelrc.

Install react-turbo and the plugin

npm i react-turbo
npm i --save-dev babel-plugin-react-turbo

Modify .babelrc

{
  "plugins": ["babel-plugin-react-turbo"]
}

You can see all changes of installation in this commit.

Example

function expensiveRandom(a) {
  let k = 0;
  for (let i = 0; i < 10_000_000; i++) k += Math.random();
  console.log('expensiveRandom:', k);
  return a;
}

function App() {
  const [a, setA] = useState(1000);
  const [b, setB] = useState(1000);

  return (
    <div className="App">
      <input value={a} type="number"
        onChange={(e) => setA(parseInt(e.target.value))}/>
      <span>{expensiveRandom(a)}</span>
      <input value={b} type="number"
        onChange={(e) => setB(parseInt(e.target.value))}/>
      <span>{b}</span>
    </div>
  );
}

expensiveRandom contains some expensive calculation. When a or b changes, the component will re-render. The rendering will be stuck because it hits <span>{expensiveRandom(a)}</span>.

With react-turbo, when b changes, only the elements that depend on b will re-render (<input value={b} and <span>{b}</span>), so expensiveRandom(a) won’t be executed.

Note that, react-turbo happens in compile time with babel, so you don’t need to modify any code to get it work.

babel-plugin-react-turbo

https://www.npmjs.com/package/babel-plugin-react-turbo

Details

The above example will be compiled to something like

function App() {
  const [$a, setA] = useAtom(1000);
  const [$b, setB] = useAtom(1000);

  return (
    <div className="App">
      <Controller a={$a}>
        {({a}) => (
          <input value={a} type="number"
            onChange={(e) => setA(parseInt(e.target.value))}/>
        )}
      </Controller>
      <Controller a={$a}>
        {({a}) => <span>{expensiveRandom(a)}</span>}
      </Controller>
      <Controller b={$b}>
        {({b}) => (
          <input value={b} type="number"
            onChange={(e) => setB(parseInt(e.target.value))}/>
        )}
      </Controller>
      <Controller b={$b}>
        {({b}) => <span>{b}</span>}
      </Controller>
    </div>
  );
}

So no matter how fast b changes, the elements depending on a won’t re-render.

$a and $b are observable atoms like Recoil, Jotai or effector.

GitHub

View Github