Вспоминаем как использовать useSelector
Рассмотрим простой "срез" состояния, который управляет списком задач в приложении:
// features/todoSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface Todo {
id: number;
text: string;
completed: boolean;
}
interface TodoState {
todos: Todo[];
}
const initialState: TodoState = {
todos: [],
};
const todoSlice = createSlice({
name: 'todo',
initialState,
reducers: {
addTodo: (state, action: PayloadAction<string>) => {
state.todos.push({
id: Date.now(),
text: action.payload,
completed: false,
});
},
toggleTodo: (state, action: PayloadAction<number>) => {
const todo = state.todos.find((t) => t.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
},
});
export const { addTodo, toggleTodo } = todoSlice.actions;
export default todoSlice.reducer;
Теперь подключим этот срез к нашему хранилищу:
// store.ts
import { configureStore } from '@reduxjs/toolkit';
import todoReducer from './features/todoSlice';
export const store = configureStore({
reducer: {
todo: todoReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Обратите внимание на тип RootState — позже мы будем использовать его для типизации useSelector.
Отлично. Теперь мы в компоненте получим список задач с помощью useSelector:
// TodoList.tsx
import React from 'react';
import { FlatList, Text, View } from 'react-native';
import { useSelector } from 'react-redux';
import { RootState } from './store';
const TodoList: React.FC = () => {
const todos = useSelector((state: RootState) => state.todo.todos);
return (
<FlatList
data={todos}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={{ padding: 10 }}>
<Text style={{ textDecorationLine: item.completed ? 'line-through' : 'none' }}>
{item.text}
</Text>
</View>
)}
/>
);
};
export default TodoList;
Вот и все! Мы подключились к глобальному состоянию и отобразили список задач. Обратите внимание, как мы использовали RootState для типизации состояния: это позволяет TypeScript следить за тем, что мы извлекаем корректные данные.
Как использовать useDispatch для изменения состояния
Теперь, когда мы можем извлекать данные, пришло время научиться что-то менять. Для этого мы используем useDispatch.
Допустим, у нас есть компонент, который добавляет новые задачи в список. Вот как это может выглядеть:
// AddTodo.tsx
import React, { useState } from 'react';
import { Button, TextInput, View } from 'react-native';
import { useDispatch } from 'react-redux';
import { addTodo } from './features/todoSlice';
const AddTodo: React.FC = () => {
const [text, setText] = useState('');
const dispatch = useDispatch();
const handleAddTodo = () => {
if (text.trim() !== '') {
dispatch(addTodo(text));
setText(''); // Очищаем поле ввода
}
};
return (
<View style={{ flexDirection: 'row', padding: 10 }}>
<TextInput
style={{ flex: 1, borderColor: '#ccc', borderWidth: 1, marginRight: 10 }}
value={text}
onChangeText={setText}
placeholder="Add a new todo"
/>
<Button title="Add" onPress={handleAddTodo} />
</View>
);
};
export default AddTodo;
мы используем здесь dispatch для отправки действия addTodo. Redux внутри обрабатывает это действие, изменяя состояние хранилища.
Пример полного приложения
Соединим все вместе в одном приложении — список задач, где вы можете добавлять и переключать задачи между выполненными и невыполненными:
// App.tsx
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './store';
import AddTodo from './AddTodo';
import TodoList from './TodoList';
import { SafeAreaView } from 'react-native';
const App: React.FC = () => {
return (
<Provider store={store}>
<SafeAreaView style={{ flex: 1 }}>
<AddTodo />
<TodoList />
</SafeAreaView>
</Provider>
);
};
export default App;
как мы используем компонент Provider, чтобы предоставить доступ к хранилищу всему приложению. Это обязательный шаг для работы с Redux.
Потенциальные ошибки и их решение
Ошибка 1: useSelector не видит данные
Если вы видите ошибку типа "Cannot read property 'todos' of undefined", вероятно, вы забыли подключить ваш редьюсер к хранилищу в configureStore.
Ошибка 2: неверная типизация
Если TypeScript жалуется на типы в useSelect или useDispatch, убедитесь, что вы экспортировали RootState и AppDispatch из вашего хранилища, а затем корректно их используете.
// Правильная типизация для useDispatch
import { useDispatch } from 'react-redux';
import { AppDispatch } from './store';
const dispatch: AppDispatch = useDispatch();
Ошибка 3: слишком частые перерендеры
Если ваш компонент рендерится слишком часто, попробуйте оптимизировать селекторы. Одна из стратегий — использовать библиотеку reselect, чтобы мемоизировать результаты вычислений.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ