unstated-nextでさくっと状態管理

# unstated-nextとは
[unstated-next](https://github.com/jamiebuilds/unstated-next)はReactのContext, Hooksのみでの状態管理をサポートする最小限なライブラリ。

<img width="770" alt="スクリーンショット 2020-01-29 21.42.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173323/f621053d-cad6-908a-b533-884cccff409c.png">


[unstated-next]()
<img width="845" alt="スクリーンショット 2020-01-29 21.28.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173323/c221c2eb-1906-45b1-84a0-cafd8aa5b769.png">

めちゃ小さい。

## Context + hooksのみで状態管理
useStateを使ってstateとその増減操作する機能を持ったカスタムフックを作成。

![context.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173323/d740fcc1-0a3a-be40-63f6-76919e89f339.gif)


```ts:useCounter
import { useState } from 'react';

const useCounter = () => {
  const [count, setCount] = useState(0);

  const decrement = () => setCount(count - 1);
  const increment = () => setCount(count + 1);
  const reset = () => setCount(0);

  return { count, decrement, increment, reset }
}

export {
  useCounter,
};
```

それを使う側、

```App.tsx
import React, { createContext } from 'react';
import { useCounter } from './hooks';
import NonUnstatedCounter from './components/NonUnstatedCounter';
import ResetButtonN from './components/NonUnstatedCounter/ResetButton';

export const CounterContext = createContext({
  count: 0,
  decrement: () => {},
  increment: () => {},
  reset: () => {}
});

const App: React.FC = () => {
  const counter = useCounter();

  return (
    <div className="contents">
      <h1>Context + hooks</h1>
      <CounterContext.Provider value={counter}>
        <NonUnstatedCounter />
        <NonUnstatedCounter />
        <ResetButtonN />
      </CounterContext.Provider>
    </div>
  );
}

export default App;
```
createContext カスタムフックであるuseCounterをcreateContextで作成したContextのProvider.valueに入れて、カスタムを扱いたいコンポーネントを包む。

中のコンポーネントでは、

```tsx:Counterコンポーネント
import React, { useContext } from 'react';
import { CounterContext } from '../../App';

const NonUnstatedCounter = () => {
  const {
    count,
    decrement,
    increment,
  } = useContext(CounterContext);

  return (
    <div className="counterN">
      <span className="decrement" onClick={decrement}>-</span>
      <span className="countNum">{count}</span>
      <span className="increment" onClick={increment}>+</span>
    </div>
  );
};

export default NonUnstatedCounter;
```

```tsx:ResetButtonコンポーネント
import React, { useContext } from 'react';
import { CounterContext } from '../../App';

const ResetButtonN = () => {
  const {
    reset,
  } = useContext(CounterContext);

  return (
    <span className="resetButton" onClick={reset}>RESET</span>
  );
};;

export default ResetButtonN;
```

reactのuseContextを使って親で作成したCounterContextを渡してContextを利用する形になる。

## unstated-nextで書き換え
![unstated.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173323/d0c8254f-f5d5-5d82-e12a-19aa4b080454.gif)
※見た目も機能も全く一緒です

```ts:useCounter
import { useState } from 'react';
import { createContainer } from 'unstated-next'; // <-- unstated-next

const useCounter = () => {
  const [count, setCount] = useState(0);

  const decrement = () => setCount(count - 1);
  const increment = () => setCount(count + 1);
  const reset = () => setCount(0);

  return { count, decrement, increment, reset }
}

const CounterContainer = createContainer(useCounter); // unstated-nextのcreateContainerで包む

export {
  useCounter,
  CounterContainer, // <-- unstated-next
};
```

unstated-nextのcreateContainerでカスタムフックをラップしてexportしている。


```App.tsx
import React from 'react';
import { CounterContainer } from './hooks';
// import NonUnstatedCounter from './components/NonUnstatedCounter';
// import ResetButtonN from './components/NonUnstatedCounter/ResetButtonN';
import UnstatedCounter from './components/UnstatedCounter';
import ResetButton from './components/UnstatedCounter/ResetButton';

// いらない子
// export const CounterContext = createContext({
//   count: 0,
//   decrement: () => {},
//   increment: () => {},
//   reset: () => {}
// });

const App: React.FC = () => {
  // const counter = useCounter(); // <-- いらない子

  return (
    <div className="contents">
      {/* <>
        <h1>Context + hooks</h1>
        <CounterContext.Provider value={counter}>
          <NonUnstatedCounter />
          <NonUnstatedCounter />
          <ResetButtonN />
        </CounterContext.Provider>
      </> */}
      <>
        <h1>unstated-next</h1>
        <CounterContainer.Provider>
          <UnstatedCounter />
          <UnstatedCounter />
          <ResetButton />
        </CounterContainer.Provider>
      </>
    </div>
  );
}

export default App;
```

createContextを使ったり、Providerにvalueを渡す必要がない。

```tsx:Counterコンポーネント
import React from 'react';
import { useContainer } from 'unstated-next';
import { CounterContainer } from '../../hooks';

const UnstatedCounter = () => {
  const {
    count,
    decrement,
    increment,
  } = useContainer(CounterContainer);

  return (
    <div className="counterN">
      <span className="decrement" onClick={decrement}>-</span>
      <span className="countNum">{count}</span>
      <span className="increment" onClick={increment}>+</span>
    </div>
  );
};

export default UnstatedCounter;
```

```tsx:ResetButtonコンポーネント
import React from 'react';
import { useContainer } from 'unstated-next'; // <-- unstated-next
import { CounterContainer } from '../../hooks';

const ResetButton = () => {
  const {
    reset,
  } = useContainer(CounterContainer); // <-- unstated-next

  return (
    <button className="resetButton" onClick={reset}>reset</button>
  );
};;

export default ResetButton;
```

Containerを使う側のコンポーネントでは、unstated-nextのuseContainerでカスタムフックを使う。
ContextをContainerに置き換えたイメージ。

なんとなくの利点としては、最小限・最適な粒度でState(Container)を管理していけば、パフォーマンスの改善が見込めるのではないかと感じました。