ReactHooks（カスタムフック）の基本系を少しずつ発展させていく

## この記事について

この記事では、ReactHooksの中のカスタムフックを利用する際、
私が考えるリファクタリングの進め方を見ていきます。

「もっとこうしたほうがいいよ」という方法がありましたら、ぜひ、ご教授いただければ幸いです。

# 対象読者

- React Hooksの書き方に悩んでいる人
- React Hooksの基本方針を知りたい人

# 対象外の内容

- useEffetcやuseContextなどの処理は含みません

# 基本形

まずは、ベースとなるアプリから

```jsx
const TodoApp = () => {
  const [title, setTitle] = useState("");
  const [description, setDescription] = useState("");

  const handleAddTodo = (e) => {
		...validation処理
    const newTodo = { title, description };
    // TODO todoを使った処理
  }

  return (
    <div>
      <input value={title} onChange={(e)=>setTitle(e.target.value)} />
      <input value={description} onChange={(e)=>setDescription(e.target.value)} />
    </div>
  );
}
```

これをベースとします。

## 最終形

```jsx
const useTodo = () => {
  const [todo, setTodo] = useState({ title: "", description: ""});
  const [errors, setErrors] = useState({});

  const validate = () => {
    let newErrors = {};
    if (!todo.title) newErrors.title = 'タイトルは必須です';
    if (todo.description > 10) newErrors.description = '説明は最低10文字入力してください';

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  }

  const addTodo = () => {
    // TODO todoを使った処理
  }

  const setTitle = (title) => {
    setTodo((prev) => ({...prev, title}));
  }

  const setDescription = (description) => {
    setTodo((prev) => ({...prev, description }));
  }

  return { todo, setTitle, setDescription, addTodo, validate, errors  };
}

const TodoApp = () => {
  const { todo, setTitle, setDescrition, addTodo, validate, errors } = useTodo();

  const handleAddTodo = (e) => {
    if (!validation) return;
    addTodo();
  }

  return (
    <div>
      // errorsを使ってエラー処理を行う
      <input value={todo.title} onChange={(e)=> setTitle(e.target.value)} />
      <input value={todo.description} onChange={(e)=> setDescription(e.target.value)} />
    </div>
  );
}
```

最終形はこんな感じ。

行数は増えてますが、再利用性などを考えるとこちらの書き方が良いと思います。

# そもそも問題点は？

まず、基本形でいいのではないか？という疑問を見ていきます。

TodoApp規模では問題ないのですが、これが大きな規模になるとやはり問題があります。

例えば、TodoAppにAI機能を付与するとします。

すると、titleやdescriptionを使って、handle系が沢山増え、

また機能を増やしてはhandleが増え、

……最終的にはTodoAppのコードが肥大化していきます。

TodoAppはあくまでも**Todo機能的**なものを**表示する場所**に抑えたいです。

そこで一つ指針を立てます。

「Hooksを利用する側が汚れないようにする」というものです。

それを基本指針として、基本系を修正していきます。

では、よろしくお願いいたします。



# まずは基本的なカスタムフックを考える

最初は基本的なカスタムフックの考え方です。
基本系を次のように、単純なstateをまとめるだけの、変更を行います。

```jsx

const useTodo = () => {
  const [title, setTitle] = useState("");
  const [description, setDescription] = useState("");

  return { title, setTitle, descrpition, setDescription };
}

const TodoApp = () => {
  const { title, setTitle, description, setDescrition } = useTodo();

  const handleAddTodo = (e) => {
		...validation処理
    const newTodo = { title, description };
    // TODO todoを使った処理
  }

  return (
    <div>
      <input value={title} onChange={(e)=>setTitle(e.target.value)} />
      <input value={description} onChange={(e)=>setDescription(e.target.value)} />
    </div>
  );
}
```

これで、todoのstateはuseTodoを見るだけで良くなりました。
stateを一括に管理できるのがHooksの良さです。



# 処理はカスタムフック内に

これで十分に見えますが、先ほどの処理では、「処理が分散されてる」という問題点があります。

handleAddTodoのnewTodoの部分は、外側で再度作るべきではありません。
これでは、useTodoを使う箇所、全てで同様な処理が起きてしまい、依存性が高くなります。

これらは、useTodo側が持っておくべき関心ごとです。

ということで、次のように変更します。

```jsx

const useTodo = () => {
  const [title, setTitle] = useState("");
  const [description, setDescription] = useState("");

  const addTodo = () => {
    const newTodo = { title, description };
    // TODO todoを使った処理
  }

  return { title, setTitle, descrpition, setDescription, addTodo  };
}

const TodoApp = () => {
  const { title, setTitle, description, setDescrition, addTodo } = useTodo();

  const handleAddTodo = (e) => {
		...validation処理
    addTodo();
  }

  return (
    <div>
      <input value={title} onChange={(e)=>setTitle(e.target.value)} />
      <input value={description} onChange={(e)=>setDescription(e.target.value)} />
    </div>
  );
}
```

useTodoの内にaddTodoという処理を持ってきました。

こうすることで、handleAdd内の処理が分散せず、
useTodoのaddTodoを利用するだけで良くなりました。

# 変数の多さを直す

次に気になるのはuseTodoがやり取りしている**変数の多さ**です。

こういう時、私は「無限に増やしたらどうなるか」を想定しています。

例えば、TODOの変数を無限に増やしたらどうなるか。

useTodoの**戻り値**と、それを**受ける変数**が無限に増えるでしょう。

結果、**TodoApp内の可読性が悪くなります**。

TodoApp内を汚す可能性は少しでも無くしたいです。

それを解決するために、やり取りする変数をオブジェクトにまとめます。

```jsx

const useTodo = () => {
  const [todo, setTodo] = useState({ title: "", description: ""});

  const addTodo = () => {
    // TODO todoを使った処理
  }

  const setTitle = (title) => {
    setTodo((prev) => ({...prev, title}));
  }

  const setDescription = (description) => {
    setTodo((prev) => ({...prev, description }));
  }

  return { todo, setTitle, setDescription, addTodo  };
}

const TodoApp = () => {
  const { todo, setTitle, setDescrition, addTodo } = useTodo();

  const handleAddTodo = (e) => {
		...validation処理
    addTodo();
  }

  return (
    <div>
      <input value={todo.title} onChange={(e)=> setTitle(e.target.value)} />
      <input value={todo.description} onChange={(e)=> setDescription(e.target.value)} />
    </div>
  );
}
```

setDescriptionなどの関数が増えましたが、
結果的にuseTodoを利用するがわのコードを汚さずに済みます。

基本指針を元にすると、こちらのほうが良いです。


# validationをまとめる

現状でも、再利用性が高く、十分に見えますが、
今後を見据えると、おそらくvalidation処理が重複するように見えます。

つまりはuseTodoを使う部分全てに、validationを書かなければいけません。

ということで、それらもuseTodoに移行しましょう。

```jsx

const useTodo = () => {
  const [todo, setTodo] = useState({ title: "", description: ""});
  const [errors, setErrors] = useState({});

  const validate = () => {
    let newErrors = {};
    if (!todo.title) newErrors.title = 'タイトルは必須です';
    if (todo.description > 10) newErrors.description = '説明は最低10文字入力してください';

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  }

  const addTodo = () => {
    // TODO todoを使った処理
  }

  const setTitle = (title) => {
    setTodo((prev) => ({...prev, title}));
  }

  const setDescription = (description) => {
    setTodo((prev) => ({...prev, description }));
  }

  return { todo, setTitle, setDescription, addTodo, validate, errors  };
}

const TodoApp = () => {
  const { todo, setTitle, setDescrition, addTodo, validate, errors } = useTodo();

  const handleAddTodo = (e) => {
    if (!validation) return;
    addTodo();
  }

  return (
    <div>
      // errorsを使ってエラー処理を行う
      <input value={todo.title} onChange={(e)=> setTitle(e.target.value)} />
      <input value={todo.description} onChange={(e)=> setDescription(e.target.value)} />
    </div>
  );
}
```

これで全てのTodoの関心ごとがuseTodoに含まれました！

# AI関連の処理を入れたい！

最後に、処理を追加するパターンを見ます。
例えば、Todoに関するAI処理をいら鯛とします。
すると、useTodo内に処理を入れるだけなので、非常に簡単です。

```jsx

const useTodo = () => {
  const [todo, setTodo] = useState({ title: "", description: ""});
  const [errors, setErrors] = useState({});

  const validate = () => {
    let newErrors = {};
    if (!todo.title) newErrors.title = 'タイトルは必須です';
    if (todo.description > 10) newErrors.description = '説明は最低10文字入力してください';

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  }

  const addTodo = () => {
    // TODO todoを使った処理
  }

  const setTitle = (title) => {
    setTodo((prev) => ({...prev, title}));
  }

  const setDescription = (description) => {
    setTodo((prev) => ({...prev, description }));
  }

  // 追加処理
  const analyzeTodo = () => { ... }

  return { todo, setTitle, setDescription, addTodo, validate, errors, analyzeTodo  };
}
```

あとは、利用したい箇所でanalyzeTodoを呼び出します。

# まとめ

今回は私なりのHooksのアプローチを見ていきました。

まとめますと、次のような方針です。

1. まずはページ最上位に全ての処理を書く。
2. 関心ごとが同じ部分をカスタムHooksとしてまとめる
3. Hooks内の操作をまとめる
4. 変数を少なくする
5. Hooks内のエラーをまとめる
6. 追加処理は、関心領域に入れる。

その他に、メモ化などの処理を考えたほうがいいので、そちらも今後見ていきます。

以上です。