Управление состоянием в React Native
Управление состоянием — ключевой аспект React Native разработки. Рассмотрим различные подходы. От простого useState до Redux и Zustand — каждый подход имеет свои use cases и компромиссы.
Local State
Локальное состояние используется для данных, которые не выходят за пределы компонента.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<View>
<Text>{count}</Text>
<Button title="+" onPress={() => setCount(c => c + 1)} />
</View>
);
}
Для простых случаев локального состояния useState достаточно. Но для общих данных нужны другие решения.
Context
Context позволяет передавать данные через дерево компонентов без пропсов.
// ThemeContext.tsx
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
return useContext(ThemeContext);
}
// Использование
function ThemedButton() {
const { theme, setTheme } = useTheme();
return (
<Button
title={theme}
onPress={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}
/>
);
}
Redux Toolkit
Redux Toolkit — официальная библиотека для Redux с упрощённым API.
npm install @reduxjs/toolkit react-redux
Redux использует однонаправленный поток данных: компоненты диспатчат действия, редюсеры обновляют состояние.
Redux Toolkit упрощает настройку Redux, автоматически создавая экшены и редюсеры:
// store.ts
import { configureStore, createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1; },
decrement: (state) => { state.value -= 1; },
incrementByAmount: (state, action) => { state.value += action.payload; },
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export const store = configureStore({
reducer: { counter: counterSlice.reducer },
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// Использование
import { useSelector, useDispatch } from 'react-redux';
import { increment } from './store';
function Counter() {
const count = useSelector((state: RootState) => state.counter.value);
const dispatch = useDispatch<AppDispatch>();
return (
<Button title={count} onPress={() => dispatch(increment())} />
);
}
// Provider
import { Provider } from 'react-redux';
import { store } from './store';
function App() {
return (
<Provider store={store}>
<MainScreen />
</Provider>
);
}
Zustand
npm install zustand
// store.ts
import { create } from 'zustand';
interface CounterStore {
count: number;
increment: () => void;
decrement: () => void;
}
const useStore = create<CounterStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
// Использование
function Counter() {
const { count, increment, decrement } = useStore();
return (
<View>
<Text>{count}</Text>
<Button title="+" onPress={increment} />
</View>
);
}
React Query
npm install @tanstack/react-query
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<UserList />
</QueryClientProvider>
);
}
function UserList() {
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: async () => {
const res = await fetch('https://api.example.com/users');
return res.json();
},
});
if (isLoading) return <Text>Loading...</Text>;
if (error) return <Text>Error: {error.message}</Text>;
return (
<FlatList
data={data}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Text>{item.name}</Text>}
/>
);
}
AsyncStorage
npm install @react-native-async-storage/async-storage
import AsyncStorage from '@react-native-async-storage/async-storage';
// Сохранение
await AsyncStorage.setItem('user', JSON.stringify(user));
// Чтение
const userJson = await AsyncStorage.getItem('user');
const user = userJson ? JSON.parse(userJson) : null;
// Удаление
await AsyncStorage.removeItem('user');
Persist с Zustand
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
const useStore = create(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{
name: 'my-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);
Заключение
Выбор инструмента зависит от сложности приложения: local state для простых компонентов, Context для theme/locale, Zustand/Redux для глобального состояния, React Query для данных с сервера.