最近のフロントエンドにおけるコンポーネント構成要素についてまとめてみた

## はじめに
最近のフロントエンド界隈では、Hooksを多用しろ！とか、Viewとロジックを分離しろ！といった流れが目立ってきていると感じてます。
私も流れに乗って、日々の開発でその考え方を積極的に取り入れて、試行錯誤を繰り返しています。
その中での大まかな**コンポーネントの構成要素**について、本記事でまとめていきたいと思います。


## 構成について
一般的なコンポーネントの構成要素として、下記の三つに大きく分けられます。

- ビジネスロジック
コンポーネントに依存しない共通のロジック
- コンポーネントロジック
コンポーネント依存のロジック
- View
画面の表示部分

### ビジネスロジック
コンポーネントに依存しない共通のロジックです。たとえば、データ取得処理、テーマの切替処理などが挙げられます。
この類の処理は、通常一つのカスタムフックにまとめると良いです。
理由は、
- コンポーネント間で使いまわせるから
- ソースの可読性が向上するから

の2点が挙げられます。

ちなみに、[React Query](https://react-query-v3.tanstack.com/)や、[RTK Query](https://redux-toolkit.js.org/rtk-query/overview)とかのライブラリは、データ取得の処理をHookとしてまとめて用意してくれるようなもので、out-of-boxに使えて非常に便利です。


　　
### コンポーネントロジック
コンポーネント依存のロジック。たとえば、clickイベント、ライフサイクルなどが挙げられます。
この類の処理は、必ずしもカスタムフックに切り出さなくても良いです。コードの記述量によって、臨機応変に決めると良いでしょう。


### View
画面の表示部分です。以上。

## さて、実例で考えてみよう（React）
こんなコードがあったとします。（[react-complete-guide-code](https://github.com/academind/react-complete-guide-code/)から拝借）
```tsx
import React, { useEffect, useState } from 'react';

import Tasks from './components/Tasks/Tasks';
import NewTask from './components/NewTask/NewTask';

function App() {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const [tasks, setTasks] = useState([]);

  const fetchTasks = async (taskText) => {
    setIsLoading(true);
    setError(null);
    try {
      const response = await fetch(
        'https://react-http-6b4a6.firebaseio.com/tasks.json'
      );

      if (!response.ok) {
        throw new Error('Request failed!');
      }

      const data = await response.json();

      const loadedTasks = [];

      for (const taskKey in data) {
        loadedTasks.push({ id: taskKey, text: data[taskKey].text });
      }

      setTasks(loadedTasks);
    } catch (err) {
      setError(err.message || 'Something went wrong!');
    }
    setIsLoading(false);
  };

  useEffect(() => {
    fetchTasks();
  }, []);

  const taskAddHandler = (task) => {
    setTasks((prevTasks) => prevTasks.concat(task));
  };

  return (
    <React.Fragment>
      <NewTask onAddTask={taskAddHandler} />
      <Tasks
        items={tasks}
        loading={isLoading}
        error={error}
        onFetch={fetchTasks}
      />
    </React.Fragment>
  );
}

export default App;

```
タスクを`fetch()`で取得し、画面上に表示する、という非常にシンプルなコンポーネントですね。
このケースだと、下記のように分類できるでしょう。

- ビジネスロジック・・・データ取得処理、それと関連するstateたち
```tsx
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const [tasks, setTasks] = useState([]);

  const fetchTasks = async (taskText) => {
    setIsLoading(true);
    setError(null);
    try {
      const response = await fetch(
        'https://react-http-6b4a6.firebaseio.com/tasks.json'
      );

      if (!response.ok) {
        throw new Error('Request failed!');
      }

      const data = await response.json();

      const loadedTasks = [];

      for (const taskKey in data) {
        loadedTasks.push({ id: taskKey, text: data[taskKey].text });
      }

      setTasks(loadedTasks);
    } catch (err) {
      setError(err.message || 'Something went wrong!');
    }
    setIsLoading(false);
  };
```

- コンポーネントロジック・・・コンポーネント依存のライフサイクル処理、イベントハンドラー処理

```tsx
  useEffect(() => {
    fetchTasks();
  }, []);

  const taskAddHandler = (task) => {
    setTasks((prevTasks) => prevTasks.concat(task));
  };
```

- View・・・returnの中身、画面の表示部分

```tsx
  return (
    <React.Fragment>
      <NewTask onAddTask={taskAddHandler} />
      <Tasks
        items={tasks}
        loading={isLoading}
        error={error}
        onFetch={fetchTasks}
      />
    </React.Fragment>
  );

```



## さいごに
このように、ロジック系とViewの分離を意識し、積極的にロジックの切り出しを行うことで、ファイルの肥大化を防ぐことができます。コード可読性や保守性の向上、といったメリットがあります。
また、ビジネスロジックをカスタムフックに切り出す（モジュール化）ことで、中に含まれるロジックを**全て理解しなければならない**、ということから開発者が解放され、短いスパンで機能の追加ができ、より優れた拡張性をもたらします。
本記事の内容が保守性の高いクリーンコードに近づけていくためのヒントとして、参考になれば幸いです。
また、ビジネスロジックの切り出し方あたりも、今後まとめて記事にしてみたいと思います。