Урок 08

Синхронизация между экранами

Когда данные меняются на одном экране, другие должны обновиться.

TanStack Query — автоматически

Если оба экрана используют useQuery с одним ключом — данные синхронизированы:

// Экран 1
const { data } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts });

// Экран 2
const { data } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts });

Изменение на одном экране → инвалидация → оба обновятся.

Инвалидация после мутации

const mutation = useMutation({
    mutationFn: createPost,
    onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['posts'] });
    },
});

Все экраны с ['posts'] перезагрузят данные.

Refetch при фокусе

useFocusEffect(
    useCallback(() => {
        queryClient.invalidateQueries({ queryKey: ['posts'] });
    }, [queryClient])
);

При возврате на экран — свежие данные.

Глобальный стор

Если данные в Zustand — все экраны видят изменения автоматически:

const count = useCartStore((s) => s.items.length);

События

Иногда нужен обмен событиями:

import { DeviceEventEmitter } from 'react-native';

// на одном экране
DeviceEventEmitter.emit('post-created', { id: 5 });

// на другом
useEffect(() => {
    const sub = DeviceEventEmitter.addListener('post-created', (data) => {
        console.log('создан пост', data.id);
    });
    return () => sub.remove();
}, []);

Мини-эмиттер

// lib/events.ts
type Listener = (...args: unknown[]) => void;

const listeners = new Map<string, Set<Listener>>();

export const events = {
    on(event: string, listener: Listener) {
        if (!listeners.has(event)) listeners.set(event, new Set());
        listeners.get(event)!.add(listener);
        return () => listeners.get(event)?.delete(listener);
    },

    emit(event: string, ...args: unknown[]) {
        listeners.get(event)?.forEach((l) => l(...args));
    },
};

Использование:

events.emit('cart-updated');

useEffect(() => events.on('cart-updated', refetch), []);

Навигация с параметрами

Передать данные при возврате:

// на экране редактирования
navigation.navigate('List', { refresh: true });

// на экране списка
useEffect(() => {
    if (route.params?.refresh) {
        refetch();
    }
}, [route.params?.refresh]);

Или navigation.popTo('List', { refresh: true }).

Оптимистичные обновления

Если данные меняются на нескольких экранах, оптимистичное обновление делает UI мгновенным:

onMutate: async (newPost) => {
    queryClient.setQueryData(['posts'], (old: Post[]) => [...old, newPost]);
}

Что выбрать

  • TanStack Query — для серверных данных
  • Zustand — для клиентского
  • Event emitter — для точечных событий
  • Параметры навигации — для разовых сигналов

Итоги

  • TanStack Query синхронизирует автоматически
  • invalidateQueries после мутаций
  • useFocusEffect для refetch
  • Zustand для клиентского состояния
  • Event emitter для точечных событий