Tags: React

React のカスタムフックでビューとロジックを分離する

React でビューとロジックを分離するためにカスタムフックを作成する。

開発環境

  • node v16.9.1
  • npm v7.21.1
  • react v17.0.2

本文

React で公式サイトを見ながら作ると、ビュー部分とロジックは同一のファイルに書かれることになると思う。
それは間違っていないと思うのだが、複数人で開発するようなケースを想定したとき、ビューとロジックは分かれているほうが都合がいい気がする。

どのようにすればビューとロジックを分離できるか調べたところ、まず関数は普通に別ファイルに切り出すことができる。
フックについてはカスタムフックとして切り出せば実現可能そうだった。

公式サイトの「独自フックの作成」によるとカスタムフックはフックを他のコンポーネントで再利用するために作る物のようなのだが、今回は単にロジック分離のためにカスタムフック化してみることにする。

カスタムフックの抽出と作成

例としてログイン画面を使う。現在のソースコードが下記の状態であり、ハイライトされているのがロジック部分にあたる。

src/Signin.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import React, { useState } from "react";
import { app } from './config/firebase';
import { getAuth, signInWithEmailAndPassword } from "firebase/auth";

function Form() {
    const [loginId, setLoginId] = useState("");
    const [password, setPassword] = useState("");
    const [errorMessage, setErrorMessage] = useState("");
    const auth = getAuth();

    const handleChange = (event) => {
        switch (event.target.name) {
            case 'loginId':
                setLoginId(event.target.value);
                break;
            case 'password':
                setPassword(event.target.value);
                break;
            default:
                console.log('key not found');
                break;
        }
    };

    const handleSubmit = (event) => {
        event.preventDefault();

        if (loginId === '' || password === '') {
            return;
        }

        signInWithEmailAndPassword(auth, loginId, password)
            .then((userCredential) => {
                const user = userCredential.user;
                console.log(user);
                window.location.href = "/input";
            })
            .catch((error) => {
                setErrorMessage("認証できませんでした。");
            });
    };

    return (
        <main>
            <form id="signin-form" onSubmit={handleSubmit}>
                <input type='text' id='loginId' name='loginId' onChange={handleChange} value={loginId} />
                <br />
                <input type='password' id='password' name='password' onChange={handleChange} value={password} />
                <br />
                <input type="submit" value="送信" />
                <div>{errorMessage}</div>
            </form>
        </main>
    );
}

function Signin() {
    return (
        <div>
            <Form />
        </div>
    );
}

export default Signin;

ここからカスタムフックとして、function Form 内の return 部以外を丸ごと別ファイルに切り出す。
カスタムフックの名称は use から始めることになっているので、名前は useSignin にした。

切り出し方法としては特に書き換え等必要なく、そのままそっくり中身を持ってきて、最後に return で使いたい物を返せばいい。

export const useSignin = () => {
  // ここにロジックをそのままそっくり移植する

  // 元の src/Signin.js 内で使いたい物を return で列挙しておくと使えるようになる。
  return [loginId, password, errorMessage, { handleChange, handleSubmit }];
};

実際に作ったファイルが下記の通り。

src/hooks/Signin/useSignin.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { useState } from "react";
import { app } from '../../config/firebase';
import { getAuth, signInWithEmailAndPassword } from "firebase/auth";

export const useSignin = () => {
    const [loginId, setLoginId] = useState("");
    const [password, setPassword] = useState("");
    const [errorMessage, setErrorMessage] = useState("");
    const auth = getAuth();

    const handleChange = (event) => {
        switch (event.target.name) {
            case 'loginId':
                setLoginId(event.target.value);
                break;
            case 'password':
                setPassword(event.target.value);
                break;
            default:
                console.log('key not found');
                break;
        }
    };

    const handleSubmit = (event) => {
        event.preventDefault();

        if (loginId === '' || password === '') {
            return;
        }

        signInWithEmailAndPassword(auth, loginId, password)
            .then((userCredential) => {
                const user = userCredential.user;
                console.log(user);
                window.location.href = "/input";
            })
            .catch((error) => {
                setErrorMessage("認証できませんでした。");
            });
    };

    return [loginId, password, errorMessage, { handleChange, handleSubmit }];
};

このカスタムフックを使うと最初のファイルは以下のように書き換えられる。

src/Signin.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React from "react";
import { useSignin } from './hooks/Signin/useSignin';

function Form() {
    const [loginId, password, errorMessage, { handleChange, handleSubmit }] = useSignin();

    return (
        <main>
            <form id="signin-form" onSubmit={handleSubmit}>
                <input type='text' id='loginId' name='loginId' onChange={handleChange} value={loginId} />
                <br />
                <input type='password' id='password' name='password' onChange={handleChange} value={password} />
                <br />
                <input type="submit" value="送信" />
                <div>{errorMessage}</div>
            </form>
        </main>
    );
}

function Signin() {
    return (
        <div>
            <Form />
        </div>
    );
}

export default Signin;

このようにロジック部分を丸ごと分離することができた。

以上