Reactにおいて、TanStack Queryを使った場合と使わない場合の比較



### TanStack Queryを使った場合

TanStack Queryを使うと、データの取得、キャッシング、同期が簡単になります。以下は、APIからデータを取得する基本的な例です。

```jsx
import React from 'react';
import { useQuery } from '@tanstack/react-query';

const fetchTodos = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos');
  if (!response.ok) {
    throw new Error('Network response was not ok');
  }
  return response.json();
};

const TodoList = () => {
  const { data, error, isLoading } = useQuery(['todos'], fetchTodos);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {data.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
};

export default TodoList;
```

### TanStack Queryを使わない場合

TanStack Queryを使わない場合、状態管理やデータの取得を自前で行う必要があります。以下は、同様の機能を実装した例です。

```jsx
import React, { useEffect, useState } from 'react';

const TodoList = () => {
  const [todos, setTodos] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchTodos = async () => {
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/todos');
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const data = await response.json();
        setTodos(data);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };

    fetchTodos();
  }, []);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
};

export default TodoList;
```

### 比較

- **TanStack Queryを使った場合**:
  - 自動的なキャッシングやリフェッチが行われる。
  - コードがシンプルで、データの取得に関するロジックが明確。
  - 状態管理（ローディング、エラーなど）が簡潔に扱える。

- **TanStack Queryを使わない場合**:
  - 状態管理やエラーハンドリングを自分で行う必要がある。
  - コードが冗長になりがちで、複雑なロジックが必要になることがある。

TanStack Queryを使った場合と使わない場合の状態管理の違いについて説明します。

### 状態管理(TanStack Queryを使った場合)

TanStack Queryを使用すると、以下のような状態管理が自動的に行われます。

1. **ローディング状態**:
   - データ取得中は`isLoading`が`true`になります。これにより、簡単にローディングインジケーターを表示できます。

2. **エラー管理**:
   - データ取得中にエラーが発生すると、`error`オブジェクトが返されます。これにより、エラーメッセージを簡単に表示できます。

3. **データのキャッシング**:
   - 取得したデータは内部的にキャッシュされ、同じクエリが再度実行される際には、キャッシュからデータが返されます。これにより、無駄なAPIリクエストを避けられます。

4. **リファッチ（再取得）**:
   - データが古くなった場合や、ウィンドウが再フォーカスされたときに自動でデータを再取得することができます。これにより、常に最新のデータを保持できます。

5. **クエリの無効化と再有効化**:
   - 特定の条件でクエリを無効化したり再有効化したりすることが可能で、柔軟なデータ管理ができます。

### 状態管理(TanStack Queryを使わない場合)

TanStack Queryを使用しない場合は、状態管理を自分で行う必要があります。

1. **ローディング状態**:
   - `useState`を使用してローディング状態を管理します。APIリクエストの開始時に`loading`を`true`に設定し、リクエスト完了後に`false`に戻す必要があります。

2. **エラー管理**:
   - 同様に、`error`という状態を作成し、APIリクエスト中にエラーが発生した場合にその状態を更新します。このため、エラーハンドリングのロジックを自分で実装する必要があります。

3. **データのキャッシング**:
   - データのキャッシングは自動では行われないため、毎回APIリクエストが行われます。これにより、不要にリクエストを行い、パフォーマンスが低下する可能性があります。

4. **リフェッチ（再取得）**:
   - ウィンドウがフォーカスされたときや、特定の条件で再取得を行うロジックを自分で実装する必要があります。これがないと、古いデータが表示され続けることになります。

5. **状態管理の複雑さ**:
   - 状態管理のコードが複雑になりがちで、特に大規模なアプリケーションでは、複数のAPI呼び出しや状態を管理するのが難しくなります。


### コード例

**1. データ取得**
```javascript
import { useQuery } from '@tanstack/react-query';

const fetchUser = async () => {
  const response = await fetch('https://api.example.com/user');
  if (!response.ok) throw new Error('Network response was not ok');
  return response.json();
};

const User = () => {
  const { data, error, isLoading } = useQuery(['user'], fetchUser);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return <div>User Name: {data.name}</div>;
};
```

**2. キャッシュ管理**
```javascript
const UserList = () => {
  const { data } = useQuery(['users'], fetchUsers);

  return (
    <ul>
      {data?.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};
```
データはキャッシュされ、再度同じクエリを実行するとキャッシュから取得されます。

**3. 自動再フェッチ**
```javascript
const UserProfile = () => {
  const { data, isFetching } = useQuery(['user'], fetchUser, {
    refetchOnWindowFocus: true,
  });

  return (
    <div>
      {isFetching && <span>Refreshing...</span>}
      <h1>{data?.name}</h1>
    </div>
  );
};
```
ウィンドウがフォーカスされると自動でデータが再取得されます。

**4. ローディング・エラーハンドリング**
```javascript
const Product = () => {
  const { data, error, isLoading } = useQuery(['product'], fetchProduct);

  if (isLoading) return <div>Loading product...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return <div>Product Name: {data.name}</div>;
};
```
ローディング中とエラー発生時の表示を管理できます。

**5. ページネーションや無限スクロール**
```javascript
const fetchPosts = async (page = 1) => {
  const response = await fetch(`https://api.example.com/posts?page=${page}`);
  return response.json();
};

const PostList = () => {
  const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
    ['posts'],
    ({ pageParam = 1 }) => fetchPosts(pageParam),
    {
      getNextPageParam: (lastPage) => lastPage.nextPage,
    }
  );

  return (
    <div>
      {data.pages.map((page) =>
        page.items.map(post => <div key={post.id}>{post.title}</div>)
      )}
      {hasNextPage && <button onClick={fetchNextPage}>Load More</button>}
    </div>
  );
};
```