
カスタムフックって何?

Atomic Designとは??

グローバルStateとは???
本記事では、上記のような悩みを実際に動くコードを見ながら確認していけるようになっております。
現在、本業でShopifyエンジニアとして勤務しながら趣味でReactの学習をしております。
学習した内容のまとめとして記事を公開しておりますので、間違っている箇所もあるかもしれませんが何か参考になればと思っております。
また、本記事は、Raise Techのフロントエンドエンジニアコースの受講記でもあります。
こちらの記事は、第9回の講義のまとめとなります。

カスタムフックについて
React
では、多くのHooks(関数)
が提供されています。
さらに、開発者が独自でHooks
を作ることができます。そのHooks
のことをカスタムフックと呼んでいます。

わざわざカスタムフックを使う理由ってなに??
- コンポーネント部分には、画面の表示を任せ、カスタムフックにはロジックの部分の記述することで責務を分けることができるようになる。
- 責務を分けることで、可読性や保守性があがる。
- テストがしやすくなる。
また、カスタムフックを使用する際には、ルールがあるので気をつけておきましょう。
ということで、実際にカスタムフックを使ってみようと思います。
今回は、モーダルの実装をカスタムフックを使って行ってみます。実際に動くコードも最後に貼り付けておきます。
カスタムフックを使わない例を記述した後に、カスタムフックにしていこうと思います。

まずは、カスタムフックを使わないときの最終的なファイル構成は、以下の通りになります。
▼カスタムフック
components
—modals
—–Modal.jsx
App.jsx
まずは、App.jsx
に下記の通り記述します。ボタンを押した時に、toggle関数
が実行されて、isShow
の状態を切り替えております。
そのstate
をModalコンポーネント
に渡しています。
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>
);
}
そして、isShow
のState
を受け取る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
に閉じ込めることができました。
コンポーネント分割について

コンポーネント分割は非常に重要な手法になります。
共通の部品をコンポーネント化することで、例えば複数のページでまたがって同じデザインのものがあるとき1度コードを書いてコンポーネント化しておくことで、そのコンポーネントを呼ぶことで同じコードを2度、3度と記述することがなくなります。
僕が今まで実務でやってきたWordPressやShopifyでもコンポーネントを分割して、必要なページに呼び出して使うということを行ってきました。
では、どの粒度でコンポーネントを作成していくのかというと、、、実はこれには答えがなくて色々なやり方が存在しています。
ここでは、個人的に気に入っているAtomic Designというコンポーネント分割の考え方について解説します。
Atomic Designでは、画面要素を5段階の粒度に分け、組み合わせてUIを構築していきます。
・Atoms(最小単位の部品)
・Molecules(複数の部品が組み合わさったもの)
・Organisms(atomやmoleculeを組み合わせて作る)
・Templates(画面のレイアウトを定義)
・Pages(テンプレートに実際のデータを流し込み、1つの画面として機能)
解説用に1枚簡単なページを作りましたのでこちらをベースに進めていきます。

Atoms

これ以上小さく分けることができない最小の部品になります。
ボタンや、アイコンなどの小さな部品となります。ここではサインインのボタンがAtomになります。
Molecules

複数の部品が組み合わさった部品となります。
ここでは、ボタンとアイコンが組み合わさって出てきているログインボタンになります。
あと、カード1枚もMoleculeとして扱ってもいいかもしれません。
Oraganisms

atomやmoleculeを組み合わせてOranismsは作られております。
ここでは、ヘッダーや、カードのまとまりがOranismとして扱われます。
Templates

ここが少しわかりにくい(自分もあまりしっくりきていない)のですが、画面のレイアウトのみを定義する役割となります。
いまデザインみるとデータが入ってきてますが、データが入ってきていない側の部分だけを作るファイルとなります。
(ワイヤーフレームに近い)
Pages

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

なんだかめんどくさそうだなぁ
実際、小さなアプリを作るぐらいだったらここまでやる必要はないみたいですが、プロジェクトが大きいとAtomic Designを採用することが多いようなので考え方だけでも知っておくといいかなと思ってます。
丁寧に解説されている英語の記事を紹介しておきます。

グローバルなState管理について
今まで扱ってきたState
は特定のコンポーネント内に記述をしておりました。(ローカルStateと呼ばれます。)
ただ、コンポーネントをまたいでState
を扱う場合にはProps
を用いて情報を渡してきました。
1つ、2つ渡すぐらいであればいいのですがまたぐコンポーネントが増えれば増えるほどProps
を渡していく作業が必要になります。(バケツリレーと揶揄されている。。。笑)
グローバル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.jsx
にprops
で赤色という情報を渡しています。
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.jsx
でApp.jsx
で作成したprops
を展開して受け取っています。
これを見て頂ければ分かると思いますが、非常に無駄な受け渡しが発生しておりますよね。Bucket1.jsx
とBucket2.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";
を読み込みます。
createContext
でContextオブジェクト
を生成します。
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.jsx
やContext2.jsx
にprops
を渡す必要がなくなったので、直接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>
</>
);
};
useContext
でColorContext
を受け取り、colorInfo
として定数を定義します。
あとは、style={colorInfo}
で受け取ってあげれば、正しくvalue
が受け取れているのが確認できると思います。

確かにこれであれば、無駄な記述も少なくなるしいいですね。
ということでcontext
の説明をさせていただきました。
自分ももう少し理解も深めるためにゴリゴリ他のサンプルを書いていって、より深ぼった記事を書いていきますね。

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