Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
0
Help us understand the problem. What is going on with this article?
@sogafish

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

More than 1 year has passed since last update.

unstated-nextとは

unstated-nextはReactのContext, Hooksのみでの状態管理をサポートする最小限なライブラリ。

スクリーンショット 2020-01-29 21.42.11.png

unstated-next
スクリーンショット 2020-01-29 21.28.17.png

めちゃ小さい。

Context + hooksのみで状態管理

useStateを使ってstateとその増減操作する機能を持ったカスタムフックを作成。

context.gif

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に入れて、カスタムを扱いたいコンポーネントを包む。

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

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;
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
※見た目も機能も全く一緒です

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を渡す必要がない。

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;
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)を管理していけば、パフォーマンスの改善が見込めるのではないかと感じました。

0
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
sogafish

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
0
Help us understand the problem. What is going on with this article?