Кеширование и стратегии
Кеш ускоряет приложение и экономит трафик.
Уровни кеша
- HTTP-кеш — заголовки
Cache-Control,ETag - TanStack Query — кеш в памяти
- Persist — кеш в AsyncStorage
- Изображения —
expo-imageкеширует - Компоненты —
memo,useMemo
staleTime и gcTime
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
staleTime: 5 * 60_000, // 5 минут данные считаются свежими
gcTime: 30 * 60_000, // 30 минут в памяти после использования
});
- staleTime — как долго данные «свежие»
- gcTime — как долго хранить неиспользуемые данные
Стратегии
Fresh — данные всегда свежие:
staleTime: Infinity
Network-only — всегда из сети:
staleTime: 0,
cacheTime: 0,
Cache-first — сначала кеш, потом сеть:
staleTime: 5 * 60_000
Stale-while-revalidate — показать старое, обновить в фоне:
staleTime: 5 * 60_000,
refetchOnMount: 'always',
Persistent cache
const persister = createAsyncStoragePersister({
storage: AsyncStorage,
key: 'react-query-cache',
throttleTime: 1000,
});
<PersistQueryClientProvider
client={queryClient}
persistOptions={{
persister,
maxAge: 24 * 60 * 60 * 1000,
dehydrateOptions: {
shouldDehydrateQuery: (query) => query.state.status === 'success',
},
}}
>
<App />
</PersistQueryClientProvider>
maxAge — как долго хранить в AsyncStorage.
Кеш изображений
expo-image:
import { Image } from 'expo-image';
<Image
source={{ uri: url }}
cachePolicy="memory-disk" // 'none' | 'disk' | 'memory' | 'memory-disk'
transition={200}
/>
Prefetch
Загрузить заранее:
queryClient.prefetchQuery({
queryKey: ['post', id],
queryFn: () => fetchPost(id),
});
В onPress перед навигацией — данные будут готовы к моменту открытия экрана.
Инвалидация
queryClient.invalidateQueries({ queryKey: ['posts'] }); // все с этим ключом
queryClient.invalidateQueries({ queryKey: ['posts', id] }); // конкретный
queryClient.invalidateQueries({ queryKey: ['posts'], exact: true }); // точное совпадение
Обновление конкретной записи
queryClient.setQueryData(['post', id], newData);
Оптимистичное обновление
onMutate: async (updated) => {
await queryClient.cancelQueries({ queryKey: ['post', updated.id] });
const previous = queryClient.getQueryData(['post', updated.id]);
queryClient.setQueryData(['post', updated.id], updated);
return { previous };
},
onError: (_, __, context) => {
queryClient.setQueryData(['post', context?.previous?.id], context?.previous);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['post'] });
},
Что кешировать
- Списки (посты, пользователи)
- Детали (профиль, товар)
- Справочники (категории, города)
- Изображения
- Результаты поиска
Что не кешировать
- Персональные данные при логауте
- Токены
- Платёжную информацию
- Live-данные (курсы, чат)
Очистка при логауте
function logout() {
queryClient.clear();
AsyncStorage.clear();
SecureStore.deleteItemAsync('token');
}
Итоги
staleTimeиgcTime- Persist для кеша между запусками
expo-imageдля изображений- Prefetch для предзагрузки
invalidateQueriesдля обновления- Очистка при логауте