📊

React ContextとJotaiとValtioとZustandでカウンターアプリのサイズ比較

2021/05/25に公開

https://twitter.com/dai_shi/status/1396976791940595713

https://twitter.com/dai_shi/status/1396976798232055808

React Contextを使ったコード

https://codesandbox.io/s/react-context-counters-eky4f

import { createContext, useContext, useState } from "react";

const createCountContext = (initialValue) => {
  const CountContext = createContext(initialValue);
  const CountProvider = ({ children }) => (
    <CountContext.Provider value={useState(initialValue)}>
      {children}
    </CountContext.Provider>
  );
  const useCount = () => useContext(CountContext);
  return { CountProvider, useCount };
};

const {
  CountProvider: CountProviderA,
  useCount: useCountA
} = createCountContext(1);
const {
  CountProvider: CountProviderB,
  useCount: useCountB
} = createCountContext(2);
const {
  CountProvider: CountProviderC,
  useCount: useCountC
} = createCountContext(3);

const CounterA = () => {
  const [count, setCount] = useCountA();
  return (
    <div>
      A: {count} <button onClick={() => setCount((c) => c + 1)}>+1</button>
    </div>
  );
};

const CounterB = () => {
  const [count, setCount] = useCountB();
  return (
    <div>
      B: {count} <button onClick={() => setCount((c) => c + 1)}>+1</button>
    </div>
  );
};

const CounterC = () => {
  const [count, setCount] = useCountC();
  return (
    <div>
      C: {count} <button onClick={() => setCount((c) => c + 1)}>+1</button>
    </div>
  );
};

const App = () => (
  <CountProviderA>
    <CountProviderB>
      <CountProviderC>
        <CounterA />
        <CounterA />
        <CounterB />
        <CounterB />
        <CounterC />
        <CounterC />
      </CountProviderC>
    </CountProviderB>
  </CountProviderA>
);

export default App;

Jotaiを使ったコード

https://codesandbox.io/s/jotai-counters-zn16r

import { atom, useAtom } from "jotai";

const countAtomA = atom(1);
const countAtomB = atom(2);
const countAtomC = atom(3);

const CounterA = () => {
  const [count, setCount] = useAtom(countAtomA);
  return (
    <div>
      A: {count} <button onClick={() => setCount((c) => c + 1)}>+1</button>
    </div>
  );
};

const CounterB = () => {
  const [count, setCount] = useAtom(countAtomB);
  return (
    <div>
      B: {count} <button onClick={() => setCount((c) => c + 1)}>+1</button>
    </div>
  );
};

const CounterC = () => {
  const [count, setCount] = useAtom(countAtomC);
  return (
    <div>
      C: {count} <button onClick={() => setCount((c) => c + 1)}>+1</button>
    </div>
  );
};

const App = () => (
  <>
    <CounterA />
    <CounterA />
    <CounterB />
    <CounterB />
    <CounterC />
    <CounterC />
  </>
);

export default App;

Valtioを使ったコード

https://codesandbox.io/s/valtio-counters-y3632

import { proxy, useSnapshot } from "valtio";

const stateA = proxy({ count: 1 });
const stateB = proxy({ count: 2 });
const stateC = proxy({ count: 3 });

const CounterA = () => {
  const { count } = useSnapshot(stateA);
  return (
    <div>
      A: {count} <button onClick={() => ++stateA.count}>+1</button>
    </div>
  );
};

const CounterB = () => {
  const { count } = useSnapshot(stateB);
  return (
    <div>
      B: {count} <button onClick={() => ++stateB.count}>+1</button>
    </div>
  );
};

const CounterC = () => {
  const { count } = useSnapshot(stateC);
  return (
    <div>
      C: {count} <button onClick={() => ++stateC.count}>+1</button>
    </div>
  );
};

const App = () => (
  <>
    <CounterA />
    <CounterA />
    <CounterB />
    <CounterB />
    <CounterC />
    <CounterC />
  </>
);

export default App;

Zustandを使ったコード

https://codesandbox.io/s/zustand-counters-9k48i

import create from "zustand";

const createCountStore = (initialValue) =>
  create((set) => ({
    count: initialValue,
    inc: () => set((prev) => ({ count: prev.count + 1 }))
  }));

const useStoreA = createCountStore(1);
const useStoreB = createCountStore(2);
const useStoreC = createCountStore(3);

const CounterA = () => {
  const count = useStoreA((state) => state.count);
  const inc = useStoreA((state) => state.inc);
  return (
    <div>
      A: {count} <button onClick={inc}>+1</button>
    </div>
  );
};

const CounterB = () => {
  const count = useStoreB((state) => state.count);
  const inc = useStoreB((state) => state.inc);
  return (
    <div>
      B: {count} <button onClick={inc}>+1</button>
    </div>
  );
};

const CounterC = () => {
  const count = useStoreC((state) => state.count);
  const inc = useStoreC((state) => state.inc);
  return (
    <div>
      C: {count} <button onClick={inc}>+1</button>
    </div>
  );
};

const App = () => (
  <>
    <CounterA />
    <CounterA />
    <CounterB />
    <CounterB />
    <CounterC />
    <CounterC />
  </>
);

export default App;

ビルドした結果

$ (cd context-counters; yarn build)

File sizes after gzip:

  41.71 KB  build/static/js/2.096dca2e.chunk.js
  784 B     build/static/js/runtime-main.1b7e5d07.js
  550 B     build/static/js/main.f99437d3.chunk.js


$ (cd jotai-counters; yarn build)

File sizes after gzip:

  44.75 KB  build/static/js/2.6faf9917.chunk.js
  779 B     build/static/js/runtime-main.8b9abacf.js
  442 B     build/static/js/main.0aff4939.chunk.js


$ (cd valtio-counters; yarn build)

File sizes after gzip:

  44.55 KB  build/static/js/2.01ebfb38.chunk.js
  782 B     build/static/js/runtime-main.436c500d.js
  447 B     build/static/js/main.94b85785.chunk.js


$ (cd zustand-counters; yarn build)

File sizes after gzip:

  44.48 KB  build/static/js/2.1770fde7.chunk.js
  783 B     build/static/js/runtime-main.1e7f31f0.js
  462 B     build/static/js/main.879b382e.chunk.js

Chart

(参考) Bundlephobia

jotai

valtio

zustand

Discussion