カスタムフック・Atomic Design・グローバルState【RaiseTech フロントコース#9】

悩んでいる人

カスタムフックって何?

悩んでいる人

Atomic Designとは??

悩んでいる人

グローバルStateとは???

本記事では、上記のような悩みを実際に動くコードを見ながら確認していけるようになっております。

現在、本業でShopifyエンジニアとして勤務しながら趣味でReactの学習をしております。

学習した内容のまとめとして記事を公開しておりますので、間違っている箇所もあるかもしれませんが何か参考になればと思っております。

また、本記事は、Raise Techのフロントエンドエンジニアコースの受講記でもあります。
こちらの記事は、第9回の講義のまとめとなります。

スポンサーリンク

目次

カスタムフックについて

Reactでは、多くのHooks(関数)が提供されています。

さらに、開発者が独自でHooksを作ることができます。そのHooksのことをカスタムフックと呼んでいます。

考える人

わざわざカスタムフックを使う理由ってなに??

なぜカスタムフックを使うのか?
  • コンポーネント部分には、画面の表示を任せ、カスタムフックにはロジックの部分の記述することで責務を分けることができるようになる。
  • 責務を分けることで、可読性や保守性があがる。
  • テストがしやすくなる。

また、カスタムフックを使用する際には、ルールがあるので気をつけておきましょう。

1. ファイル名・関数名をuseから始める(慣習)
2. 関数は基本的に、useCallbackでメモ化する
3. 基本的に、変数や関数を複数返却するので、配列やオブジェクトでまとめて返却する

ということで、実際にカスタムフックを使ってみようと思います。

今回は、モーダルの実装をカスタムフックを使って行ってみます。実際に動くコードも最後に貼り付けておきます。

カスタムフックを使わない例を記述した後に、カスタムフックにしていこうと思います。

まずは、カスタムフックを使わないときの最終的なファイル構成は、以下の通りになります。

▼カスタムフック
components
modals
—–Modal.jsx
App.jsx

まずは、App.jsxに下記の通り記述します。ボタンを押した時に、toggle関数が実行されて、isShowの状態を切り替えております。

そのstateModalコンポーネントに渡しています。

import "./styles.css";
import { useState } from "react";
import { Modal } from "./components/modals/Modal";

export default function App() {
  const [isShow, isSetShow] = useState(false);
  const toggle = () => {
    isSetShow(!isShow);
  };
  return (
    <div className="App">
      <h1>モーダルを開く</h1>
      <button onClick={toggle}>ボタン</button>
      <Modal isShow={isShow} />
    </div>
  );
}

そして、isShowStateを受け取るModalコンポーネントの記述です。

3項演算子でtrueのときに表示、falseのときにはnullを返すように記述しています。

export const Modal = (props) => {
  const { isShow } = props;
  return (
    <>
      {isShow ? (
        <div className="modal__content">
          <img src="https://source.unsplash.com/random/" alt="" />
        </div>
      ) : null}
    </>
  );
};

いったんこれで、先程のモーダルの表示・非表示をボタンを押すことで制御ができるようになりました。

では、ここからカスタムフックにしていこうと思います。

下記が最終的なカスタムフック化したときのファイル構成になります。

▼カスタムフック
components
modals
—–Modal.jsx
hooks
—–useModal.jsx
App.jsx

実はあまりやることはないのですが、App.jsxに記述しているモーダルを開く・閉じるの処理の部分をuseModal.jsxに書き写します。

import { useState } from "react";

export const useModal = () => {
  const [isShow, isSetShow] = useState(false);
  const toggle = () => {
    isSetShow(!isShow);
  };
  return { isShow, toggle };
};

そして、App.jsxでカスタムフックをインポートします。

import "./styles.css";
import { useModal } from "./hooks/useModal";
import { Modal } from "./components/modals/Modal";
export default function App() {
  const { isShow, toggle } = useModal();
  return (
    <div className="App">
      <h1>モーダルを開く</h1>
      <button onClick={toggle}>ボタン</button>
      <Modal isShow={isShow} />
    </div>
  );
}

これでカスタムフック化は完了になります。

const { isShow, toggle } = useModal();useModal()returnされてきているものを受け取って、ボタンとモーダル部分に渡しているのが分かると思います。

これで、App.jsxでは画面の表示だけを行うためのコンポーネントになり、ロジック部分をuseModal.jsxに閉じ込めることができました。

コンポーネント分割について

https://atomicdesign.bradfrost.com/chapter-2/

コンポーネント分割は非常に重要な手法になります。
共通の部品をコンポーネント化することで、例えば複数のページでまたがって同じデザインのものがあるとき1度コードを書いてコンポーネント化しておくことで、そのコンポーネントを呼ぶことで同じコードを2度、3度と記述することがなくなります。

僕が今まで実務でやってきたWordPressShopifyでもコンポーネントを分割して、必要なページに呼び出して使うということを行ってきました。

では、どの粒度でコンポーネントを作成していくのかというと、、、実はこれには答えがなくて色々なやり方が存在しています。

ここでは、個人的に気に入っているAtomic Designというコンポーネント分割の考え方について解説します。

Atomic Designでは、画面要素を5段階の粒度に分け、組み合わせてUIを構築していきます。
・Atoms(最小単位の部品)
・Molecules(複数の部品が組み合わさったもの)
・Organisms(atomやmoleculeを組み合わせて作る)
・Templates(画面のレイアウトを定義)
・Pages(テンプレートに実際のデータを流し込み、1つの画面として機能)

解説用に1枚簡単なページを作りましたのでこちらをベースに進めていきます。

Atoms

これ以上小さく分けることができない最小の部品になります。

ボタンや、アイコンなどの小さな部品となります。ここではサインインのボタンがAtomになります。

Molecules

複数の部品が組み合わさった部品となります。

ここでは、ボタンとアイコンが組み合わさって出てきているログインボタンになります。

あと、カード1枚もMoleculeとして扱ってもいいかもしれません。

Oraganisms

atommoleculeを組み合わせてOranismsは作られております。

ここでは、ヘッダーや、カードのまとまりがOranismとして扱われます。

Templates

ここが少しわかりにくい(自分もあまりしっくりきていない)のですが、画面のレイアウトのみを定義する役割となります。

いまデザインみるとデータが入ってきてますが、データが入ってきていない側の部分だけを作るファイルとなります。
(ワイヤーフレームに近い)

Pages

Pagesでは、実際にページにデータを流し込み画面を表示させます。

考える人

なんだかめんどくさそうだなぁ

実際、小さなアプリを作るぐらいだったらここまでやる必要はないみたいですが、プロジェクトが大きいとAtomic Designを採用することが多いようなので考え方だけでも知っておくといいかなと思ってます。

丁寧に解説されている英語の記事を紹介しておきます。

あわせて読みたい
Atomic Design Methodology | Atomic Design by Brad Frost Learn how to create and maintain digital design systems, allowing your team to roll out higher quality, more consistent UIs faster than ever before.

グローバルなState管理について

今まで扱ってきたStateは特定のコンポーネント内に記述をしておりました。(ローカルStateと呼ばれます。)

ただ、コンポーネントをまたいでStateを扱う場合にはPropsを用いて情報を渡してきました。

1つ、2つ渡すぐらいであればいいのですがまたぐコンポーネントが増えれば増えるほどPropsを渡していく作業が必要になります。(バケツリレーと揶揄されている。。。笑)

グローバルStateを用いることでこの課題を解決することができるようになります。

Propsを使わずに任意のコンポーネントから同一のStateを参照・更新することができるようになります。

グローバルStateを扱うための方法を紹介します。

・ContextでのState管理
→Reactが標準で備えている機能

・RecoilでのState管理
→Facebookが2020年5月に公開したまだまだ新しい機能

・ReduxでのState管理
→一番良く使われているState管理ライブラリ

今回は、ContextでのState管理の使い方についてご紹介します。

ContextでのState管理について

最終的に、上記のページをバケツリレーで渡した場合と、useContextを使った2パターンで見ていきます。

バケツリレーのパターン

今回のファイル構成になります。

components
bucket
—–Bucket1.jsx
—–Bucket2.jsx
—–Bucket3.jsx
App.jsx

まずは、App.jsxにコードを記述します。

export default function App() {
  return (
    <div className="App">
      <h1>バケツリレーでのState管理</h1>
      <Bucket1 color="red" />
    </div>
  );
}

<Bucket1 color="red" />Bucket1.jsxpropsで赤色という情報を渡しています。

export const Bucket1 = (props) => {
  const { color } = props;
  return (
    <>
      <h2>バケツページ1です!</h2>
      <Bucket2 color={color} />
    </>
  );
};

Bucket1.jsxでは、App.jsxから受け取った情報をBucket2.jsxに渡します。

export const Bucket2 = (props) => {
  const { color } = props;
  return (
    <>
      <h2>バケツページ2です!</h2>
      <Bucket3 color={color} />
    </>
  );
};

Bucket2.jsxでは、Bucket1.jsxから受け取った情報をBucket3.jsxに渡します。

export const Bucket3 = (props) => {
  const { color } = props;
  return (
    <>
      <h2 style={{ color }}>バケツページ3です!</h2>
    </>
  );
};

Bucket3.jsxApp.jsxで作成したpropsを展開して受け取っています。

これを見て頂ければ分かると思いますが、非常に無駄な受け渡しが発生しておりますよね。
Bucket1.jsxBucket2.jsxでは使用することもないpropsを受け取ってしまっているので可読性も下がってしまいます。

これを解消するために、useContextを使用します。

useContextのパターン

今回のファイル構成になります。

components
context
—–Context1.jsx
—–Context2.jsx
—–Context3.jsx
provider
—–ColorProvider.jsx
App.jsx

まずは、providerフォルダの中にColorProvider.jsxというファイルを作ります。

import { createContext } from "react";

export const ColorContext = createContext({});

export const ColorProvider = (props) => {
  const { children } = props;
  const colorInfo = { color: "blue" };
  return (
    <ColorContext.Provider value={colorInfo}>{children}</ColorContext.Provider>
  );
};

import { createContext } from "react";を読み込みます。

createContextContextオブジェクト を生成します。

ColorProviderを定義します。

次に、App.jsxに下記のように記述します。

import { Context1 } from "./components/context/Context1";
import { ColorProvider } from "./components/provider/ColorProvider";
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <h1>ContextでのState管理</h1>
      <ColorProvider>
        <Context1 />
      </ColorProvider>
    </div>
  );
}

ColorProviderでラップされたコンポーネントでは、ColorContext.Providerに渡されたvalueをアクセスすることができるようになります。

バケツリレーの方式と違って、ここではContext1.jsxContext2.jsxpropsを渡す必要がなくなったので、直接Context3.jsxで受け取るための記述をします。

import { useContext } from "react";
import { ColorContext } from "../provider/ColorProvider";

export const Context3 = () => {
  const colorInfo = useContext(ColorContext);
  console.log(colorInfo);
  return (
    <>
      <h2 style={colorInfo}>バケツページ3です!</h2>
    </>
  );
};

useContextColorContextを受け取り、colorInfoとして定数を定義します。

あとは、style={colorInfo}で受け取ってあげれば、正しくvalueが受け取れているのが確認できると思います。

考える人3

確かにこれであれば、無駄な記述も少なくなるしいいですね。

ということでcontextの説明をさせていただきました。

自分ももう少し理解も深めるためにゴリゴリ他のサンプルを書いていって、より深ぼった記事を書いていきますね。

CodeSandbox
raisetech09_グローバルState - CodeSandbox raisetech09_グローバルState by takahiro-okada using react, react-dom, react-scripts

まとめ

今回もかなりボリュームがありました、、、。

カスタムフックは思っていたより簡単でしたが、グローバルStateをうまく使いこなせていないので、もう少し触っていかないといけないなぁと。

React楽しい〜〜〜〜〜〜〜!!

スポンサーリンク

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
目次