7.2. Survey - Архитектура приложения
Общая архитектура
Проект Survey использует типичную архитектуру React-приложения с клиентской маршрутизацией и интеграцией с Firebase для хранения данных и аутентификации. Проект следует принципам компонентного подхода и разделения ответственности.
Основные архитектурные слои приложения:
- Презентационный слой (UI) - React-компоненты, отвечающие за отображение интерфейса
- Слой бизнес-логики - функции и хуки для обработки данных и управления состоянием
- Слой данных - взаимодействие с API и базой данных Firebase
Компонентная структура
Приложение построено по принципу компонентной архитектуры, где каждый компонент отвечает за определенную функциональность. Компоненты организованы иерархически и взаимодействуют друг с другом через props и контекст.
Ключевые компоненты
App- корневой компонент, определяющий структуру маршрутизацииSurveyCard- основной контейнер для отображения опросаQuestionInterface- компонент для отображения вопроса и интерфейса ответаNavigationControls- компонент для навигации по вопросамSurveyCompleted- компонент, отображаемый при завершении опроса
Схема основных компонентов:
App
└── BrowserRouter
└── Routes
└── Route (/survey)
├── SurveyCard
│ ├── Header
│ ├── QuestionInterface
│ │ └── [AnswerInterface]
│ └── NavigationControls
├── SurveyCompleted
├── SurveyEnded
├── PreparationSteps
├── DifficultyModal
└── CompletionScreen
Маршрутизация
Для маршрутизации в приложении используется библиотека React Router. Основная структура маршрутизации определена в компоненте App.
Пример маршрутизации
<BrowserRouter>
<Routes>
<Route path="/" element={<Navigate to="/survey" />} />
<Route
path="/survey"
element={
// Основной контент приложения
}
/>
<Route path="*" element={<Navigate to="/survey" />} />
</Routes>
</BrowserRouter>
Управление состоянием
В проекте для управления состоянием используется встроенный в React хук useState. Основные состояния приложения определены в компоненте App и передаются в дочерние компоненты через props.
Ключевые состояния приложения:
surveyData- данные опроса, полученные с сервераstep- текущий шаг прохождения опросаcurrentQuestionIndex- индекс текущего вопросаanswers- объект с ответами пользователя
Пример управления состоянием
// Состояния в компоненте App
const [surveyData, setSurveyData] = useState<any>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<boolean>(false);
const [step, setStep] = useState<number>(0);
const [currentQuestionIndex, setCurrentQuestionIndex] = useState<number>(0);
const [answers, setAnswers] = useState<Record<string, any>>({});
// Обработчик изменения ответа
const handleAnswerChange = (questionId: string, value: any) => {
setAnswers(prev => ({
...prev,
[questionId]: value
}));
Работа с данными
Приложение взаимодействует с данными через API-функции и Firebase. Данные опросов загружаются асинхронно и сохраняются в состоянии приложения.
Загрузка данных опроса
Пример загрузки данных
// Функция загрузки данных опроса в App.tsx
useEffect(() => {
const fetchSurveyData = async () => {
try {
const urlParams = new URLSearchParams(window.location.search);
const surveyId = urlParams.get('surveyId');
const userId = urlParams.get('userId');
if (!surveyId || !userId) {
setError(true);
setLoading(false);
return;
}
const data = await getSurveyData(surveyId, userId);
if (!data || data.length === 0 || !data[0].questions) {
setError(true);
setLoading(false);
return;
}
setSurveyData(data);
// Загрузка сохраненного состояния опроса, если есть
loadSavedState(surveyId, userId);
} catch (error) {
console.error('Error fetching survey data:', error);
setError(true);
} finally {
setLoading(false);
}
};
fetchSurveyData();
}, []);
Сохранение ответов
Ответы пользователя сохраняются в локальное хранилище для обеспечения возможности продолжить опрос позже, а также отправляются на сервер при завершении опроса.
Пример сохранения ответов
// Функция сохранения состояния опроса в surveyPersistence.ts
export const saveSurveyState = (
surveyId: string,
userId: string,
answers: Record<string, any>,
currentQuestionIndex: number
) => {
try {
const stateKey = `surveyState_${surveyId}_${userId}`;
const state = {
answers,
currentQuestionIndex,
timestamp: new Date().toISOString()
};
localStorage.setItem(stateKey, JSON.stringify(state));
return true;
} catch (error) {
console.error('Error saving survey state:', error);
return false;
}
Обработка ошибок
В приложении реализована обработка ошибок на различных уровнях:
- Состояние
errorдля отображения ошибок загрузки данных - Перехват ошибок в асинхронных операциях с использованием try/catch
- Проверка полученных данных на валидность
Пример обработки ошибок
// Пример обработки ошибок при загрузке данных
try {
const data = await getSurveyData(surveyId, userId);
if (!data || data.length === 0 || !data[0].questions) {
setError(true);
setLoading(false);
return;
}
setSurveyData(data);
} catch (error) {
console.error('Error fetching survey data:', error);
setError(true);
} finally {
setLoading(false);
}
// Отображение ошибки в UI
{error && (
<div className="error-container">
<h2>Произошла ошибка при загрузке опроса</h2>
<p>Пожалуйста, проверьте параметры URL или попробуйте позже.</p>
</div>
)}