見出し画像

説明をしながらreact+typescriptでカスタムフックを作っていく

カスタムフックについて

reactで開発をしているとビュー、ロジックを同じページに書くことができて便利である一方再利用できなかったりロジックがあまりにも長かったりするとコード自体がとても見にくいといった課題があります。

それらの課題を解決するためにカスタムフックを使いビュー・ロジックを分離させ、コードを見やすくしましょう!

プロジェクト作成

とりあえずプロジェクトを作っていきます。今回はreact+typescriptを使っていきます。もちろんnextjsで作って行っても大丈夫です!

mkdir addapp
cd addapp
yarn create react-app --template typescript .

作っていくものですが足し算をするようなアプリを作っていきます。簡単なアプリですが実装を通してイメージは掴むことができると思います。

手順ですがまずカスタムフックを使わない状態で実装してみてそこからカスタムフックを使った場合どのように作っていくか、どのように書き換えていくかを説明しながら進めていきます。

カスタムフックなしでの実装

まずカスタムフックなしで実装してみました。以下のようにしてみました。app.tsxに書いています。

import { useEffect, useState } from 'react';
import './App.css';

function App() {
 const [numA, setNumA] = useState(0);
 const [numB, setNumb] = useState(0);
 const [result, setResult] = useState(0);

 useEffect(() => {
   setResult(numA + numB);
 }, [numA, numB]);

 return (
   <>
     <input type='number' value={numA} onChange={(e) => setNumA(e.target.valueAsNumber)} />
     <input type='number' value={numB} onChange={(e) => setNumB(e.target.valueAsNumber)} />
     <h1>{`result : ${result}`}</h1>
   </>
 );
}

export default App;

このようにロジック、ビューが一つのファイルに書かれてしまっていてこのページに機能を増やしていくと読みにくくなってしまいます。

カスタムフックを使って実装

カスタムフックなしで実装ができたところで次はカスタムフックを使って実装してみます!以下のようになりました。

src/app.tsx

import './App.css';
import useAdd from './hooks/useAdd';

function App() {
 const { numA, numB, result, changeNumA, changeNumB } = useAdd();

 return (
   <>
     <input type='number' value={numA} onChange={changeNumA} />
     <input type='number' value={numB} onChange={changeNumB} />
     <h1>{`result : ${result}`}</h1>
   </>
 );
}

export default App;

src/hooks/useAdd.ts

import { ChangeEvent, useEffect, useState } from 'react';

const useAdd = () => {
 const [numA, setNumA] = useState(0);
 const [numB, setNumb] = useState(0);
 const [result, setResult] = useState(0);

 useEffect(() => {
   setResult(numA + numB);
 }, [numA, numB]);

 const changeNumA = (e: ChangeEvent<HTMLInputElement>) => {
   setNumA(e.target.valueAsNumber);
 };

 const changeNumB = (e: ChangeEvent<HTMLInputElement>) => {
   setNumb(e.target.valueAsNumber);
 };

 return { numA, numB, result, changeNumA, changeNumB };
};

export default useAdd;

いくつかポイントがあるので書きます。

1. hooksを書く場所について

今回はsrc/hooks内にカスタムフックを書きました。どうしてもフックが増えてくるとどのファイルに何が書かれてあるかわからなくなるのでhooksでなくても良いですがなんだかの方法で分けるべきだと思います。

2.setを返り値としなかった

カスタムフックを使っていない時は特に関数を作ることなく直接値を変更していましたがカスタムフックを使う時にはchangeNumA,changeNumBといった関数を作りました。関数を作らず以下のようにも書けたはずです。

  return { numA, numB, result, setNumA, setNumB};

これは個人的に2つの理由があります。

一つ目はロジックが増えた・変わった時に対応できるようにした時のことを考えてです。例えばsetNumAにnumAが10以上の時はそれ以上増やすことができないようにしたくなったとしましょう。どのように実装すれば良いでしょうか?おそらくchangeNumAのような関数を作りnumAの数を判定するようや仕組みを作る必要があるかと思います。また実装後はsetNumAを使っていた箇所をその関数に置き換えないといけません。1,2箇所ならまだしも10,20箇所あったら時間がかかって面倒です。それを回避するために関数にしています。

二つ目は思わぬ値が入らないようにするためです。今回の足し算アプリでもし誤字があったらどうでしょう?jsxの量が多ければその誤字には気づきにくいと思います。

下ではvalueAsNumberのrをつけ忘れています。

 <input type='number' value={numA} onChange={(e) => setNumA(e.target.valueAsNumber)} />
 <input type='number' value={numB} onChange={(e) => setNumB(e.target.valueAsNumbe)} />

例自体が極端でこの場合はエディタのエラーで気づくことができるとは思いますがなんだかの理由でエディタでもエラーが出ないような間違い方をしてしまうとデバッグに時間がかかります。そのつもりはなくても間違った値を入れてしまうといったことも考えられます。

3.イベントの型付け

カスタムフックのポイントと言うよりかはtypescriptのポイントかもしれませんがこの場合eの型推論ができないので型付けをする必要があります。

  const changeNumB = (e: ChangeEvent<HTMLInputElement>) => {

以上3つがポイントです。

まとめ

アプリケーション自体が大きくなるにつれロジック部のコードも増えてきます。そのようななかでロジック・ビューを一緒に書いてしまうとどうしても管理が難しくなってしまい思わぬバグを生む可能性が出てきます。それを防ぐためにもカスタムフックを使いコードを管理しやすいようにしましょう!

この記事が気に入ったらサポートをしてみませんか?