Комментарии 32
Очень хорошая статья, по ходу чтения появилось несколько вопросов:
1) А backend данные тоже отдает "нормализированными"? Могли бы вы привести пример данных, которые сервис возвращает SPA
2) Смотрели ли вы в сторону Relay или других подобных решений? Что о них думаете?
3) Разработка с redux требует написания большого количества подобного кода. Используете ли какие-нибудь инструменты, которые упрощают жизнь?
Я в своем относительно крупном проекте пришел к очень похожему способу использования редукса, как описано у вас.
1) А backend данные тоже отдает «нормализированными»? Могли бы вы привести пример данных, которые сервис возвращает SPA
В hh с бекендом у нас отличные отношения, что позволяет нам для разработки договариваться о нужном формате, который устроит всех. Соответственно при необходимости бекенд отдает данные сразу же нормализованными.
Например, на странице сотрудников, если ее запрашивать по ajax бек вернет информацию только о сотрудниках:
[
{
id: 1,
name: "Артем",
tests: [10]
}
]
На странице тестов получаем тесты в аналогичном формате. Нам остается только преобразовать массив в объект, чтобы иметь быстрый доступ:
employees: {
status: COMPLETE,
list: [
{
id: 1,
name: ‘Георгий’,
testResults: [123]
}
]
},
tests: {
123: {
id: 123,
score: 5
speed: 146,
description: ‘...’
...
}
}
2) Смотрели ли вы в сторону Relay или других подобных решений? Что о них думаете?
Да, смотрели. Перед стартом проекта, рассматривали разные библиотеки (redux, flux-овские, relay). Остановились на redux, так как он гибкий, легкий и позволяет подстроить систему под наши нужды.
3) Разработка с redux требует написания большого количества подобного кода. Используете ли какие-нибудь инструменты, которые упрощают жизнь?
Да, согласен. Это особенно заметно при написании функционала, задачи которого «возьми с сервера, положи в стор» (и забудь :).
На текущий момент это у нас не вызывало боли или неудобств при разработке. Мы используем небольшие самописные утилиты, которые немного упрощают написание кода. Из более глобального — пока решили не брать.
Спасибо за ответ :) Я примерно к тому же тоже пришел. Предполагаю, что graphql+relay делают очень похожие вещи, только нормализацию переносят с бэкенда на фронт.
Relay у меня не зашел, Хотя концептуально очень классная штука, но уж больно неповоротливая и негибкая. Для HelloWorld отлично работает, но шаг влево или вправо и все — удачи в поисках решений
Relay у меня не зашел, Хотя концептуально очень классная штука, но уж больно неповоротливая и негибкая. Для HelloWorld отлично работает, но шаг влево или вправо и все — удачи в поисках решений
Это и послужило главной причиной выбора redux. Мы вольны строить архитектуру так, как это будет удобна нам.
я предпочитаю даже с redux делать нормализацию на фронте, не смотря на то, что бекенд пишу я же) Для нормализации использую Normalizr
тоже смотрел в сторону Relay, но он действительно выглядит неповоротливым. конечно, это палка о двух концах: endless boilerplate (redux) vs do as I tell you (relay), ничего нового
У вас во всех примерах запрос выглядит так
fetch(`/employees/${id}`)
// Считываем ответ сервера
.then(checkStatus)
// Парсим
.then(result => result.json())
Вы действительно каждый раз вызываете проверку статуса и разбор json?
Почему не пользуетесь библиотеками, которые делают это за вас? Например, с axios код заменится на такой
axios.get('/employees/${id}')
Но да, у нас эта цепочка используется во многих местах, так как у нас нет особой боли от этих трех строк промисов.
Видимо до тех пор, пока не придется парсить json еще и для ошибок сервера, где статус не 200.
Тогда три строки превратятся во что-то большее.
Если рассмотреть axios, то в нем немало функционала, в котором мы не нуждаемся сейчас. На текущий момент времени нам более чем хватает нативной реализации\полифила + пары функций, одна из которых checkStatus (который обрабатывает статус ответа, делает throw при ошибке).
Поэтому нам просто нет нужды нести к себе в проект эту библиотеку.
fetch — это встроенный в браузер API.
Axois, также как и Superagent, или ajax-часть jQuery — это сторонние библиотеки.
Даже новое-удобное fetch-API все равно вынуждает делать много разных вещей руками
- Проверять статус сервера
- Парсить json, причем в fetch функция
.json
еще и асинхронная, что добавляет проблем - Аналогично для отправки, нужно руками вызывать JSON.stringify() для тела POST-запроса
- Чтобы работать с куками, нужно выставить специальный флаг
- Нельзя отметить отправленный запрос
В общем, если у вас в проекте нет никакой ajax-экзотики, а вы просто передаете json на сервер и обратно, то лучше использовать какую-нибудь библиотеку. Fetch слишком абстрактный и вокруг него все равно придется городить велосипед вспомогательных функций.
А какую именно библиотеку выбрать — это уже на ваше усмотрение.
И еще в разделе про обработку ошибок в actions не понял, почему нельзя написать один try-catch на всех
export function loadEmployee(id) {
return async dispatch => {
dispatch(fetchEmployee());
try {
const res = await fetch(`/employees/${id}`);
checkStatus(res);
json = await getJson(res);
dispatch(receiveEmployee(json));
} catch () {
dispatch(releaseEmployee());
dispatch(errorRecord(t('employee.error')));
}
};
}
В примере два try-catch, чтобы показать, что обрабатывая ошибки рендеринга и серверных запросов раздельно — код обработчика ошибок у нас все равно одинаковый в подавляющем большинстве случаев.
Но вот вопрос:
export default compose(
connect(
state => state.location,
{loadManagers}
),
title('managers', 'title.base'),
composeRestricted({user: true})
)(ManagersPage);
Я правильно понимаю, что здесь в итоге мы получим матрешку из 4х компонентов вложенных друга?
Добавить apollo-cleint и apollo-server, немного подшаманить — и вот у вас на клиенте в redux уже есть готовый json…
Ох уж мне этот прогресс…
Отвечу на тот вопрос, который понял)
Здесь есть два пути.
Первый способ: один редьюсер — много разных типов action (которые относятся к разным объектам)
В редьюсерах (в импортах) прописываем список action types, на которые реагирует редьюсер.
Например, есть редьюсе progress, который отображает статус загрузки страницы (прелоадер по сути)
В этом редьюсере мы подписываемся на большое количество событий — это и загрузка сотрудников (FETCH_EMPLOYEES) и загрузка тестов (FETCH_TESTS) и т. д. Соответственно по COMPLETE_EMPLOYEES, COMPLETE_TESTS прелоадер завершается.
Второй способ: один редьюсер === один тип action (который относится только к одному объекту)
На примере того же прогресса — заводим отдельные события START_PROGRESS\END_PROGRESS. И перед загрузкой сотрудников\тестов и после их загрузки диспатчатся в том числе и эти action.
Мы у себя в разработке приняли первый вариант.
1) Он избавляет от последовательного вызова лишнего набора action.
2) Проблем между неявной связностью не получаем, так как зависимости явно прописываются в импортах (а если нужны для редьюсера кастомные данные, то тогда создаем отдельные action, да)
Второй способ от связности данных нас все равно не спасает, так как связность будет или на уровне action types, либо в логике action creators.
@xnim, посмотрите на метод разработки переиспользуемых компонентов для react+redux
Замечу, что redux больше об идеологии и подходе к разработке. На такой идеологии можно строить и MobX приложение.
На проекте крупнее среднего, с использованием практически любого фреймворка будет возникать большая часть вопросов, которые мы в статье разобрали.
Спорить, почему redux\relay\mobx\angular\backbone\super-puper-framework-3000, а не что-то иное можно долго. Каждая из сторон будет в чем-то права, но в чем-то нет.
Поддержка проекта на redux это совсем не сложно. Головной болью не страдали :) И решили поделиться тем, как можно хорошо построить приложение, избежав лишних телодвижений.
Выбрав redux в качестве основного инструмента и идеологии по работе с состоянием нашего приложения мы не пожалели.
this.props.loadManagers(this.props.location);
}
Кстати, правильно ли я понимаю что судя по коду контейтера, после его отображения и запроса данных через XHR, компонент еще крутит индикатор загрузки?
Перед его рендерингом, делаем запрос за данными. В этот момент в сторе появляется информация, что нужен прелоадер: https://habrahabr.ru/company/hh/blog/310524/#comment_9820312
Поэтому, нет, не боролись, так как сейчас нас поведение устраивает
Если же вы о декораторе title, который был приведен в качестве одного из примеров, то аналогично вопросу axios vs http — у react-helmet много функций, в которых мы не нуждаемся. В таком случае лучше сделать маленькую «утилиту»\компонент, который решит потребности. А если требования изменятся, и нужен будет больший набор возможностей, то окей, перейдем.
Нянчим проект на React-redux с пелёнок