React でデータ取得を最適化:TanStack Query 基礎からキャッシュ戦略まで実装
React アプリケーションでデータ取得を行う際、useEffect と useState の組み合わせでローディング状態やエラーハンドリングを実装するのは大変ですよね。TanStack Query(旧 React Query)を使えば、これらの煩雑な処理をシンプルに記述できます。本記事では、TanStack Query の基礎から、実務で役立つキャッシュ戦略までを段階的に解説していきます。
背景
React でアプリケーションを開発する際、API からデータを取得する処理は避けて通れません。従来の方法では、useEffect フックを使ってコンポーネントのマウント時にデータを取得し、useState でローディング状態やエラー状態を管理する必要がありました。
typescript// 従来の実装例
import { useEffect, useState } from 'react';
interface User {
id: number;
name: string;
email: string;
}
typescript// データ取得処理
function UserList() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
// データ取得を開始
setLoading(true);
fetch('/api/users')
.then((res) => res.json())
.then((data) => {
setUsers(data);
setLoading(false);
})
.catch((err) => {
setError(err);
setLoading(false);
});
}, []); // 空の依存配列でマウント時のみ実行
// 以下、レンダリング処理...
}
上記のコードには以下のような問題があります。
| # | 課題 | 説明 |
|---|---|---|
| 1 | ボイラープレートの多さ | ローディング、エラー、データの 3 つの状態を毎回管理する必要がある |
| 2 | キャッシュ機能の欠如 | 同じデータを複数のコンポーネントで使う際、毎回 API を呼び出してしまう |
| 3 | データの更新管理の複雑さ | データを更新した後、関連する全てのコンポーネントに反映させるのが困難 |
| 4 | 再取得のタイミング制御 | ウィンドウフォーカス時やネットワーク再接続時の自動再取得ができない |
以下の図は、従来の方法での課題を示しています。
mermaidflowchart TB
componentA["コンポーネント A"] -->|API 呼び出し| apiCall1["GET /api/users"]
componentB["コンポーネント B"] -->|API 呼び出し| apiCall2["GET /api/users"]
componentC["コンポーネント C"] -->|API 呼び出し| apiCall3["GET /api/users"]
apiCall1 --> server["API サーバー"]
apiCall2 --> server
apiCall3 --> server
server -->|同じデータ| apiCall1
server -->|同じデータ| apiCall2
server -->|同じデータ| apiCall3
style server fill:#f96,stroke:#333
style componentA fill:#9cf,stroke:#333
style componentB fill:#9cf,stroke:#333
style componentC fill:#9cf,stroke:#333
このように、各コンポーネントが独立してデータを取得するため、同じデータに対して複数回 API リクエストが発生してしまいます。これはサーバーへの負荷を増やし、ユーザー体験も低下させる原因となるでしょう。
課題
React でのデータ取得において、開発者が直面する主な課題は以下の通りです。
状態管理の煩雑さ
データ取得には必ず「取得中」「成功」「失敗」という 3 つの状態が存在します。これらを毎回 useState で管理するのは非効率的です。
typescript// 3つの状態を管理する必要がある
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
キャッシュの実装難易度
同じデータを複数箇所で使う場合、自前でキャッシュ機構を実装するのは複雑です。キャッシュの有効期限や更新タイミングの制御も考慮する必要があります。
データの同期問題
あるコンポーネントでデータを更新した際、他のコンポーネントにも反映させる仕組みが必要になります。グローバルステートを使う方法もありますが、設計が複雑化しがちです。
以下の図は、データ同期の課題を表しています。
mermaidstateDiagram-v2
[*] --> DataFetch: ユーザー操作
DataFetch --> Loading: API リクエスト
Loading --> Success: レスポンス成功
Loading --> ErrorState: レスポンス失敗
Success --> Stale: 時間経過
Stale --> DataFetch: 再取得
ErrorState --> Retry: リトライ
Retry --> Loading
Success --> [*]
ErrorState --> [*]
この状態管理を全て手動で実装するのは大変な作業です。特に、データが古くなったタイミングを判断して再取得する処理は、実装が複雑になりやすいでしょう。
パフォーマンスの最適化
不要な API 呼び出しを減らし、ユーザー体験を向上させるには、以下のような機能が必要です。
| # | 機能 | 説明 |
|---|---|---|
| 1 | デデュープ(重複排除) | 同時に同じリクエストが発生した場合、1 回だけ実行する |
| 2 | バックグラウンド更新 | 古いデータを表示しながら、裏で新しいデータを取得する |
| 3 | 楽観的更新 | API レスポンスを待たずに UI を先に更新する |
| 4 | 自動リトライ | エラー時に自動的に再試行する |
これらの機能を自前で実装するのは非常に困難です。そこで TanStack Query の出番となります。
解決策
TanStack Query は、React アプリケーションにおけるサーバーステート管理を劇的に簡素化するライブラリです。データの取得、キャッシュ、同期、更新を自動的に処理してくれます。
TanStack Query の導入
まずはパッケージをインストールします。
bashyarn add @tanstack/react-query
次に、アプリケーションのルートに QueryClient を設定します。
typescript// QueryClient のインポート
import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
typescript// QueryClient のインスタンスを作成
const queryClient = new QueryClient({
defaultOptions: {
queries: {
// デフォルトのキャッシュ時間を5分に設定
staleTime: 5 * 60 * 1000,
// エラー時に3回まで自動リトライ
retry: 3,
},
},
});
typescript// アプリケーション全体をプロバイダーでラップ
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* アプリケーションのコンポーネント */}
<UserList />
</QueryClientProvider>
);
}
QueryClientProvider でアプリケーションをラップすることで、配下の全てのコンポーネントで TanStack Query の機能を使えるようになります。
基本的なデータ取得
TanStack Query を使った基本的なデータ取得の実装を見ていきましょう。
typescript// useQuery フックをインポート
import { useQuery } from '@tanstack/react-query';
typescript// データ取得関数を定義
const fetchUsers = async (): Promise<User[]> => {
const response = await fetch('/api/users');
// レスポンスのエラーチェック
if (!response.ok) {
throw new Error('ユーザー情報の取得に失敗しました');
}
return response.json();
};
typescript// コンポーネント内で useQuery を使用
function UserList() {
const { data, isLoading, error } = useQuery({
// 一意のクエリキー
queryKey: ['users'],
// データ取得関数
queryFn: fetchUsers,
});
// ローディング状態の表示
if (isLoading) return <div>読み込み中...</div>;
// エラー状態の表示
if (error) return <div>エラー: {error.message}</div>;
// データの表示
return (
<ul>
{data?.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
従来の方法と比較すると、コード量が大幅に削減されていることがわかります。ローディング状態やエラーハンドリングが自動的に管理されるため、開発者はビジネスロジックに集中できるでしょう。
以下の図は、TanStack Query を使った場合のデータフローを示しています。
mermaidflowchart LR
compA["コンポーネント A"] -->|クエリ| cache["Query Cache"]
compB["コンポーネント B"] -->|クエリ| cache
compC["コンポーネント C"] -->|クエリ| cache
cache -->|キャッシュミス| fetch["fetch 関数"]
fetch -->|API 呼び出し| server["API サーバー"]
server -->|レスポンス| fetch
fetch -->|データ保存| cache
cache -->|キャッシュヒット| compA
cache -->|キャッシュヒット| compB
cache -->|キャッシュヒット| compC
style cache fill:#9f9,stroke:#333
style server fill:#f96,stroke:#333
複数のコンポーネントが同じ queryKey でデータを要求しても、実際の API 呼び出しは 1 回だけ行われます。これにより、サーバーへの負荷が大幅に削減されるのです。
データの更新(Mutation)
データの作成、更新、削除には useMutation フックを使用します。
typescript// useMutation フックをインポート
import {
useMutation,
useQueryClient,
} from '@tanstack/react-query';
typescript// ユーザー作成関数を定義
const createUser = async (
newUser: Omit<User, 'id'>
): Promise<User> => {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newUser),
});
if (!response.ok) {
throw new Error('ユーザーの作成に失敗しました');
}
return response.json();
};
typescript// コンポーネント内で useMutation を使用
function CreateUserForm() {
const queryClient = useQueryClient();
const mutation = useMutation({
// データ更新関数
mutationFn: createUser,
// 成功時の処理
onSuccess: () => {
// ユーザーリストのキャッシュを無効化して再取得
queryClient.invalidateQueries({
queryKey: ['users'],
});
},
});
const handleSubmit = (
e: React.FormEvent<HTMLFormElement>
) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
// mutation を実行
mutation.mutate({
name: formData.get('name') as string,
email: formData.get('email') as string,
});
};
return (
<form onSubmit={handleSubmit}>
<input name='name' placeholder='名前' required />
<input
name='email'
type='email'
placeholder='メール'
required
/>
<button type='submit' disabled={mutation.isPending}>
{mutation.isPending
? '作成中...'
: 'ユーザーを作成'}
</button>
{mutation.isError && (
<div>エラー: {mutation.error.message}</div>
)}
</form>
);
}
onSuccess コールバック内で invalidateQueries を呼び出すことで、関連するクエリのキャッシュを無効化し、自動的に最新のデータを再取得できます。これにより、データの一貫性が保たれるのです。
具体例
実際のプロジェクトで使える、より実践的な実装例を見ていきましょう。
キャッシュ戦略の設定
TanStack Query では、クエリごとに詳細なキャッシュ戦略を設定できます。
typescript// 個別のクエリ設定例
function UserDetail({ userId }: { userId: number }) {
const { data, isLoading } = useQuery({
queryKey: ['user', userId], // ユーザーIDを含めた一意のキー
queryFn: () => fetchUser(userId),
// キャッシュの設定
staleTime: 10 * 60 * 1000, // 10分間はキャッシュを新鮮と見なす
gcTime: 30 * 60 * 1000, // 30分間はメモリに保持(旧 cacheTime)
refetchOnWindowFocus: true, // ウィンドウフォーカス時に再取得
refetchOnReconnect: true, // ネットワーク再接続時に再取得
});
if (isLoading) return <div>読み込み中...</div>;
return (
<div>
<h2>{data?.name}</h2>
<p>{data?.email}</p>
</div>
);
}
各設定項目の意味を表にまとめます。
| # | オプション | 説明 | 推奨値 |
|---|---|---|---|
| 1 | staleTime | データが新鮮と見なされる時間 | 頻繁に変更されるデータ: 0〜1 分あまり変わらないデータ: 5〜10 分 |
| 2 | gcTime | メモリに保持する時間 | staleTime の 2〜3 倍 |
| 3 | refetchOnWindowFocus | ウィンドウフォーカス時の再取得 | ユーザー関連データ: true静的データ: false |
| 4 | refetchOnReconnect | ネットワーク再接続時の再取得 | 通常は true |
これらの設定を適切に行うことで、パフォーマンスとデータの鮮度のバランスを取れます。
楽観的更新の実装
楽観的更新を使うと、API レスポンスを待たずに UI を即座に更新できます。これによりユーザー体験が大幅に向上するでしょう。
typescript// 楽観的更新の型定義
interface UpdateUserParams {
userId: number;
updates: Partial<User>;
}
typescript// ユーザー更新の mutation 実装
function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ userId, updates }: UpdateUserParams) =>
fetch(`/api/users/${userId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates),
}).then((res) => res.json()),
// mutation 実行前の処理
onMutate: async ({ userId, updates }) => {
// 進行中のクエリをキャンセル
await queryClient.cancelQueries({
queryKey: ['user', userId],
});
// 現在のデータを取得(ロールバック用)
const previousUser = queryClient.getQueryData([
'user',
userId,
]);
// 楽観的にデータを更新
queryClient.setQueryData(
['user', userId],
(old: User | undefined) => {
if (!old) return old;
return { ...old, ...updates };
}
);
// ロールバック用のデータを返す
return { previousUser };
},
// エラー時のロールバック
onError: (err, variables, context) => {
// 元のデータに戻す
if (context?.previousUser) {
queryClient.setQueryData(
['user', variables.userId],
context.previousUser
);
}
},
// 成功時もエラー時も実行される処理
onSettled: (data, error, variables) => {
// クエリを再取得して最新の状態にする
queryClient.invalidateQueries({
queryKey: ['user', variables.userId],
});
},
});
}
typescript// 楽観的更新の使用例
function UserEditForm({ user }: { user: User }) {
const updateUser = useUpdateUser();
const handleNameChange = (newName: string) => {
// UI は即座に更新される
updateUser.mutate({
userId: user.id,
updates: { name: newName },
});
};
return (
<input
value={user.name}
onChange={(e) => handleNameChange(e.target.value)}
disabled={updateUser.isPending}
/>
);
}
楽観的更新のフローを図で確認しましょう。
mermaidsequenceDiagram
participant UI as UI コンポーネント
participant Cache as Query Cache
participant API as API サーバー
UI->>Cache: 1. 現在のデータを保存
UI->>Cache: 2. 楽観的にデータを更新
Cache->>UI: 3. 新しいデータを即座に反映
UI->>API: 4. API リクエスト送信
alt 成功時
API->>UI: 5a. レスポンス成功
UI->>Cache: 6a. 最新データで再取得
else エラー時
API->>UI: 5b. エラーレスポンス
UI->>Cache: 6b. 元のデータにロールバック
UI->>UI: 7b. エラーメッセージ表示
end
このように、楽観的更新では UI を先に更新し、API がエラーを返した場合のみロールバックします。成功率の高い操作では、ユーザーは待ち時間を感じることなく操作を続けられるのです。
ページネーションの実装
大量のデータを扱う場合、ページネーションは必須です。TanStack Query でのページネーション実装を見ていきます。
typescript// ページネーション用の型定義
interface PaginatedResponse<T> {
data: T[];
totalCount: number;
pageCount: number;
currentPage: number;
}
typescript// ページネーション付きデータ取得関数
const fetchUsers = async (
page: number
): Promise<PaginatedResponse<User>> => {
const response = await fetch(
`/api/users?page=${page}&limit=10`
);
if (!response.ok)
throw new Error('データの取得に失敗しました');
return response.json();
};
typescript// ページネーション対応コンポーネント
function PaginatedUserList() {
const [page, setPage] = useState(1);
const { data, isLoading, isPlaceholderData } = useQuery({
queryKey: ['users', page],
queryFn: () => fetchUsers(page),
// 前のページのデータを表示し続ける
placeholderData: (previousData) => previousData,
});
// 次のページを先読み
const queryClient = useQueryClient();
useEffect(() => {
if (data && page < data.pageCount) {
queryClient.prefetchQuery({
queryKey: ['users', page + 1],
queryFn: () => fetchUsers(page + 1),
});
}
}, [data, page, queryClient]);
if (isLoading) return <div>読み込み中...</div>;
return (
<div>
<ul style={{ opacity: isPlaceholderData ? 0.5 : 1 }}>
{data?.data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
<div>
<button
onClick={() => setPage((p) => Math.max(1, p - 1))}
disabled={page === 1}
>
前へ
</button>
<span>
ページ {page} / {data?.pageCount}
</span>
<button
onClick={() => setPage((p) => p + 1)}
disabled={page >= (data?.pageCount ?? 1)}
>
次へ
</button>
</div>
</div>
);
}
prefetchQuery を使うことで、ユーザーが次のページに移動する前にデータを先読みできます。これにより、ページ遷移時のローディング時間を大幅に削減できるでしょう。
無限スクロールの実装
Twitter や Instagram のような無限スクロールも、useInfiniteQuery を使えば簡単に実装できます。
typescript// useInfiniteQuery をインポート
import { useInfiniteQuery } from '@tanstack/react-query';
typescript// 無限スクロール用のデータ取得関数
const fetchInfiniteUsers = async ({ pageParam = 1 }) => {
const response = await fetch(
`/api/users?page=${pageParam}&limit=20`
);
if (!response.ok)
throw new Error('データの取得に失敗しました');
return response.json();
};
typescript// 無限スクロールコンポーネント
function InfiniteUserList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
} = useInfiniteQuery({
queryKey: ['users-infinite'],
queryFn: fetchInfiniteUsers,
// 次のページ番号を取得
getNextPageParam: (lastPage) => {
return lastPage.currentPage < lastPage.pageCount
? lastPage.currentPage + 1
: undefined;
},
// 初期ページ番号
initialPageParam: 1,
});
if (isLoading) return <div>読み込み中...</div>;
return (
<div>
{data?.pages.map((page, i) => (
<div key={i}>
{page.data.map((user: User) => (
<div key={user.id}>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
))}
</div>
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage
? '読み込み中...'
: hasNextPage
? 'さらに読み込む'
: 'すべて表示されました'}
</button>
</div>
);
}
useInfiniteQuery は、ページデータを配列として管理し、fetchNextPage を呼び出すたびに新しいページを追加していきます。hasNextPage で次のページの有無を判定できるため、実装がシンプルになります。
カスタムフックでの再利用
共通のデータ取得ロジックはカスタムフックにまとめることで、コードの再利用性が向上します。
typescript// カスタムフック: ユーザー一覧取得
export function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: async () => {
const res = await fetch('/api/users');
if (!res.ok)
throw new Error('ユーザー一覧の取得に失敗');
return res.json();
},
staleTime: 5 * 60 * 1000,
});
}
typescript// カスタムフック: 個別ユーザー取得
export function useUser(userId: number) {
return useQuery({
queryKey: ['user', userId],
queryFn: async () => {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok)
throw new Error('ユーザー情報の取得に失敗');
return res.json();
},
enabled: !!userId, // userId が存在する場合のみ実行
});
}
typescript// カスタムフック: ユーザー作成
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (newUser: Omit<User, 'id'>) => {
const res = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newUser),
});
if (!res.ok) throw new Error('ユーザーの作成に失敗');
return res.json();
},
onSuccess: () => {
// ユーザー一覧を再取得
queryClient.invalidateQueries({
queryKey: ['users'],
});
},
});
}
typescript// カスタムフックの使用例
function UserManagement() {
const { data: users, isLoading } = useUsers();
const createUser = useCreateUser();
const handleCreate = (userData: Omit<User, 'id'>) => {
createUser.mutate(userData);
};
if (isLoading) return <div>読み込み中...</div>;
return (
<div>
<h2>ユーザー管理</h2>
<ul>
{users?.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
{/* フォームコンポーネント */}
</div>
);
}
カスタムフックを使うことで、コンポーネントはデータ取得の詳細を知る必要がなくなります。これにより、テストもしやすくなるでしょう。
DevTools の活用
TanStack Query DevTools を使うと、クエリの状態をリアルタイムで確認できます。
bashyarn add @tanstack/react-query-devtools
typescript// DevTools のインポート
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
typescript// アプリケーションに DevTools を追加
function App() {
return (
<QueryClientProvider client={queryClient}>
<UserManagement />
{/* 開発環境でのみ表示される */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
DevTools を使うと、以下の情報を確認できます。
| # | 確認できる情報 | 用途 |
|---|---|---|
| 1 | クエリの状態 | データが fresh か stale かを確認 |
| 2 | キャッシュの内容 | 保存されているデータを確認 |
| 3 | クエリの実行履歴 | いつデータが取得されたかを追跡 |
| 4 | エラー情報 | 失敗したクエリの詳細を確認 |
DevTools は開発時のデバッグに非常に役立ちます。本番環境では自動的に除外されるため、安心して使えるでしょう。
まとめ
TanStack Query を使うことで、React アプリケーションにおけるデータ取得が劇的にシンプルになります。本記事で解説した内容をまとめましょう。
TanStack Query の主な利点は以下の通りです。
- コード量の削減: ローディング、エラー、データの状態管理が自動化される
- 自動キャッシュ: 同じデータへの複数回の API 呼び出しを防ぐ
- 柔軟な設定: staleTime や gcTime で細かいキャッシュ戦略を設定可能
- 楽観的更新: UI を即座に更新して、ユーザー体験を向上
- 強力な機能: ページネーション、無限スクロール、先読みなどが簡単に実装できる
実装の際は、以下のポイントを意識すると良いでしょう。
- queryKey の設計: 一意で分かりやすいキーを使う
- 適切なキャッシュ時間: データの性質に応じて staleTime を調整する
- カスタムフック化: 共通のロジックは再利用可能にする
- エラーハンドリング: ユーザーにわかりやすいエラーメッセージを表示する
- DevTools の活用: 開発時は DevTools でクエリの状態を確認する
TanStack Query は、単なるデータ取得ライブラリではありません。サーバーステート管理の複雑さを抽象化し、開発者がビジネスロジックに集中できる環境を提供してくれます。ぜひプロジェクトに導入して、その威力を体感してみてください。
初めは useQuery と useMutation から始めて、徐々に高度な機能を取り入れていくことをお勧めします。本記事が、あなたの React 開発をより快適にする手助けになれば幸いです。
関連リンク
articleReact でデータ取得を最適化:TanStack Query 基礎からキャッシュ戦略まで実装
articleReact クリーンアーキテクチャ実践:UI・アプリ・ドメイン・データの責務分離
articleReact フック完全チートシート:useState から useTransition まで用途別早見表
articleReact 開発環境の作り方:Vite + TypeScript + ESLint + Prettier 完全セットアップ
articleReact とは? 2025 年版の特徴・強み・実務活用を一気に理解する完全解説
articleESLint を Yarn + TypeScript + React でゼロから構築:Flat Config 完全手順(macOS)
articleReact でデータ取得を最適化:TanStack Query 基礎からキャッシュ戦略まで実装
articleAnsible Jinja2 テンプレート速攻リファレンス:filters/tests/macros
articlePython Dev Containers 完全レシピ:再現可能な開発箱を VS Code で作る
articleStorybook で Zustand をモックする:Controls 連動とシナリオ駆動 UI
articlePrisma を Monorepo で使い倒す:パス解決・generate の共有・依存戦略
articleプラグイン競合の特定術:WordPress で原因切り分けを高速化する手順
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来