异步数据流 #
Recoil 的异步支持 #
Recoil 原生支持异步操作,无需额外中间件。主要通过以下方式处理异步:
- 异步 Selector
- Atom 的异步默认值
- Atom Effects
- useRecoilValueLoadable
异步 Selector #
基本用法 #
jsx
import { selector } from 'recoil';
const userQueryState = selector({
key: 'userQuery',
get: async ({ get }) => {
const userId = get(userIdState);
const response = await fetch(`/api/users/${userId}`);
return response.json();
},
});
错误处理 #
jsx
const userQueryState = selector({
key: 'userQuery',
get: async ({ get }) => {
const userId = get(userIdState);
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
if (response.status === 404) {
throw new Error('User not found');
}
throw new Error(`Failed to fetch user: ${response.status}`);
}
return response.json();
},
});
依赖其他状态 #
jsx
const userIdState = atom({
key: 'userId',
default: 1,
});
const userQueryState = selector({
key: 'userQuery',
get: async ({ get }) => {
const userId = get(userIdState);
const response = await fetch(`/api/users/${userId}`);
return response.json();
},
});
当 userIdState 变化时,userQueryState 会自动重新获取数据。
数据获取模式 #
1. 简单查询 #
jsx
const dataQueryState = selector({
key: 'dataQuery',
get: async () => {
const response = await fetch('/api/data');
return response.json();
},
});
2. 参数化查询 #
jsx
import { selectorFamily } from 'recoil';
const userQueryState = selectorFamily({
key: 'userQuery',
get: (userId) => async () => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
},
});
function UserProfile({ userId }) {
const user = useRecoilValue(userQueryState(userId));
return <div>{user.name}</div>;
}
3. 分页查询 #
jsx
const pageState = atom({
key: 'page',
default: 1,
});
const pageSizeState = atom({
key: 'pageSize',
default: 10,
});
const paginatedQueryState = selector({
key: 'paginatedQuery',
get: async ({ get }) => {
const page = get(pageState);
const pageSize = get(pageSizeState);
const response = await fetch(`/api/data?page=${page}&size=${pageSize}`);
return response.json();
},
});
4. 搜索查询 #
jsx
const searchQueryState = atom({
key: 'searchQuery',
default: '',
});
const searchResultsState = selector({
key: 'searchResults',
get: async ({ get }) => {
const query = get(searchQueryState);
if (!query.trim()) {
return [];
}
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
return response.json();
},
});
5. 防抖搜索 #
jsx
const searchQueryState = atom({
key: 'searchQuery',
default: '',
});
const debouncedSearchQueryState = selector({
key: 'debouncedSearchQuery',
get: async ({ get }) => {
const query = get(searchQueryState);
await new Promise(resolve => setTimeout(resolve, 300));
return query;
},
});
const searchResultsState = selector({
key: 'searchResults',
get: async ({ get }) => {
const query = get(debouncedSearchQueryState);
if (!query.trim()) {
return [];
}
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
return response.json();
},
});
使用异步数据 #
方式一:Suspense #
jsx
import { Suspense } from 'react';
import { useRecoilValue } from 'recoil';
function UserProfile() {
const user = useRecoilValue(userQueryState);
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<UserProfile />
</Suspense>
);
}
方式二:useRecoilValueLoadable #
jsx
import { useRecoilValueLoadable } from 'recoil';
function UserProfile() {
const userLoadable = useRecoilValueLoadable(userQueryState);
switch (userLoadable.state) {
case 'loading':
return <div>Loading...</div>;
case 'hasError':
return <div>Error: {userLoadable.contents.message}</div>;
case 'hasValue':
return (
<div>
<h2>{userLoadable.contents.name}</h2>
<p>{userLoadable.contents.email}</p>
</div>
);
}
}
数据刷新 #
使用 refresh #
jsx
import { useRecoilRefresher_UNSTABLE } from 'recoil';
function UserProfile() {
const user = useRecoilValue(userQueryState);
const refresh = useRecoilRefresher_UNSTABLE(userQueryState);
return (
<div>
<h2>{user.name}</h2>
<button onClick={refresh}>Refresh</button>
</div>
);
}
使用 useRecoilCallback #
jsx
import { useRecoilCallback } from 'recoil';
function RefreshButton() {
const refresh = useRecoilCallback(({ refresh }) => () => {
refresh(userQueryState);
});
return <button onClick={refresh}>Refresh Data</button>;
}
数据预取 #
jsx
function prefetchUser(userId) {
return useRecoilCallback(({ snapshot }) => async () => {
snapshot.getPromise(userQueryState(userId));
});
}
function UserListItem({ userId }) {
const prefetch = prefetchUser(userId);
return (
<div onMouseEnter={prefetch}>
User {userId}
</div>
);
}
并行数据获取 #
jsx
const userQueryState = selectorFamily({
key: 'userQuery',
get: (id) => async () => {
const response = await fetch(`/api/users/${id}`);
return response.json();
},
});
const allUsersQueryState = selector({
key: 'allUsersQuery',
get: async ({ get }) => {
const userIds = [1, 2, 3, 4, 5];
const users = await Promise.all(
userIds.map(id => get(userQueryState(id)))
);
return users;
},
});
依赖链 #
jsx
const userIdState = atom({
key: 'userId',
default: 1,
});
const userQueryState = selector({
key: 'userQuery',
get: async ({ get }) => {
const userId = get(userIdState);
const response = await fetch(`/api/users/${userId}`);
return response.json();
},
});
const userPostsQueryState = selector({
key: 'userPostsQuery',
get: async ({ get }) => {
const user = get(userQueryState);
const response = await fetch(`/api/users/${user.id}/posts`);
return response.json();
},
});
缓存策略 #
默认缓存 #
jsx
const queryState = selector({
key: 'query',
get: async () => {
const response = await fetch('/api/data');
return response.json();
},
});
LRU 缓存 #
jsx
const queryState = selector({
key: 'query',
get: async () => {
const response = await fetch('/api/data');
return response.json();
},
cachePolicy_UNSTABLE: {
eviction: 'lru',
maxSize: 10,
},
});
禁用缓存 #
jsx
const queryState = selector({
key: 'query',
get: async () => {
const response = await fetch('/api/data');
return response.json();
},
cachePolicy_UNSTABLE: {
eviction: 'most-recent',
},
});
实战示例:完整的数据获取方案 #
jsx
import { atom, selector, useRecoilValueLoadable, useRecoilState } from 'recoil';
const filterState = atom({
key: 'filter',
default: {
category: 'all',
sortBy: 'date',
page: 1,
},
});
const dataQueryState = selector({
key: 'dataQuery',
get: async ({ get }) => {
const filter = get(filterState);
const params = new URLSearchParams({
category: filter.category,
sortBy: filter.sortBy,
page: filter.page,
});
const response = await fetch(`/api/data?${params}`);
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.status}`);
}
return response.json();
},
});
function DataList() {
const [filter, setFilter] = useRecoilState(filterState);
const dataLoadable = useRecoilValueLoadable(dataQueryState);
const handleCategoryChange = (category) => {
setFilter(prev => ({ ...prev, category, page: 1 }));
};
const handlePageChange = (page) => {
setFilter(prev => ({ ...prev, page }));
};
return (
<div>
<div className="filters">
<select
value={filter.category}
onChange={(e) => handleCategoryChange(e.target.value)}
>
<option value="all">All</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
</select>
</div>
{dataLoadable.state === 'loading' && (
<div className="loading">Loading...</div>
)}
{dataLoadable.state === 'hasError' && (
<div className="error">
Error: {dataLoadable.contents.message}
</div>
)}
{dataLoadable.state === 'hasValue' && (
<>
<ul className="data-list">
{dataLoadable.contents.items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
<div className="pagination">
<button
disabled={filter.page === 1}
onClick={() => handlePageChange(filter.page - 1)}
>
Previous
</button>
<span>Page {filter.page}</span>
<button
disabled={dataLoadable.contents.items.length < 10}
onClick={() => handlePageChange(filter.page + 1)}
>
Next
</button>
</div>
</>
)}
</div>
);
}
总结 #
Recoil 异步数据流的核心要点:
| 特性 | 说明 |
|---|---|
| 异步 Selector | 原生支持 async/await |
| 自动依赖追踪 | 依赖变化自动重新获取 |
| 缓存 | 自动缓存查询结果 |
| 加载状态 | Loadable 或 Suspense |
下一步,让我们学习 状态持久化,了解如何持久化存储状态。
最后更新:2026-03-28