Comments 15
Есть решение лучше — не писать мегабайты кода. :-)
Насчёт реакта — вы теоретизируете или реально использовали его в таком сценарии? Я вот слышал про проблемы с проверкой чексум, из-за несовпадения которых реакт таки перерендеривает всё заново. Также есть проблема изоморфной подготовки данных (изоморфные модели?).
Насчет подготовки данных, то с этим не так все плохо, никто не запрещает на сервере сделать middleware наподобии:
app.use(function(req, res) {
req.store = configrureStore()
res.renderMarkup = function() {
res.send(renderFullPage(<App />, req.store.getState())
}
})
И далее в каком-то роуте:
app.get('/some', async function(req, res) {
req.store.dispatch(await loadSome())
req.store.dispatch(await doAnotherAsync())
res.renderMarkup()
})
app.get('*', (req, res) => {
res.renderMarkup()
})
Пример странный, но можно использовать асинхронные вещи.
У вас получается код одинакового наполнения стора разный для клиента и сервера. И вам необходимо поддерживать их точную эквивалентность.
class BlogPage extends Component {
componentDidMount() {
this.props.fetchPosts(this.props.params.page);
}
}
BlogPage.dispatchOnServer = [
params => fetchPosts(params.page)
];
и на сервере используется функция которая резолвит все dispatchOnServer компонентов:
export default (dispatch, components, params) => {
const needs = components.reduce((prev, current) =>
(current ? (current.dispatchOnServer || []).concat(prev) : prev),
[]
);
const promises = needs.map(fn => {
let action = fn(params);
if (isFunction(action)) {
action = action(dispatch);
} else {
dispatch(action);
}
if (!('promise' in action)) {
throw new Error('dispatchOnServer action MUST have \'promise\' key');
}
return action.promise;
});
return Promise.all(promises);
};
При этом action'ы выглядят так:
export function fetchPosts(offset) {
return dispatch => dispatch({
type: actions.FETCH_POSTS,
offset,
promise: fetch(`${constants.API}/posts?offset=${offset}&limit=${constants.POSTS_PER_PAGE}`)
.then(responseData => dispatch({
type: actions.FETCH_POSTS_SUCCESS,
posts: responseData.data,
total: responseData.total
}))
.catch(error => dispatch({
type: actions.FETCH_POSTS_FAILURE,
error: apiHelpers.formatResponseError(error)
}))
});
}
Ее использование:
app.get('*', (req, res) => {
const memoryHistory = createMemoryHistory(req.url);
const store = initReduxStore(memoryHistory);
const location = req.url;
const history = syncHistoryWithStore(memoryHistory, store);
match({ routes, location, history }, (error, redirectLocation, renderProps) => {
if (error) {
return res
.status(500)
.send(error.message);
}
if (redirectLocation) {
return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
}
if (renderProps) {
return fetchComponentsData(store.dispatch, renderProps.components, renderProps.params)
.then(renderTemplate.bind(null, store, renderProps))
.then(dataToServing => res
.status(dataToServing.status)
.end(dataToServing.html)
)
.catch(renderError => res.status(500).end(renderError.message));
}
return res.status(404).send('Not found');
});
});
Это позволяет держать всю логику наполнения стора в компоненте, хоть и по разному для клиентской части и сервера. Ничего удобнее я пока не видел, хотя хотелось бы конечно =)
На мой взгляд, гораздо более полезная плюшка изоморфности — это решение проблем с поисковиками, потому что мы пишем код только один раз, и он дает один и тот же результат и поисковику и человеку в браузере. Это избавляет нас от костыльных решений типа генерации снапшотов страниц по крону, или хуже того, написания второго приложения специально для поисковиков.
Если у браузера ресурсов мало, то и процесс будет долгий
В том и дело, что слишком много «если». Особенно в 2016-м году, когда даже телефоны имеют 4-ядерные процессоры. А если у меня мощный компьютер, а интернет так себе? Тогда мне быстрее будет получить данные через json и отрендерить страницу у себя, чем тянуть полностью весь html с сервера (и опять же, не в 100% случаях это будет так).
ничего не мешает кэшировать html, который отдает сервер
Кэшировать отрендеренный сервером html на стороне клиента? В таком случае, никто не мешает и кэшировать html, отрендеренный клиентом при первой загрузке страницы.
Мне кажется решение проблем с поисковиками вытекает как следствие из того, что мы можем быстро отдать первый раз отрендеренную страницу.
Нет, решение проблем с поисковиками вытекает из того, что мы пишем один и тот же код для сервера и клиента, и используем его и там, и там. И это позволяет нам не тратить время на создание дополнительных решений специально для поисковиков (и для всех других нужд, которые могут потребовать отрендеренную страницу без участия браузера). То, что страница рендерится сервером в некоторых случаях быстрее, это просто приятный бонус.
Изоморфность может понадобиться для решения довольно специфического круга задач, когда, к примеру, требуется отрендерить на сервере и закэшировать частые запросы к выводу элементов каталога, которые должны быть проиндексированы в таком виде поисковиком. И тут Реакт очень далеко не оптимален, как решение. В случае, когда вы создается SPA, работающее с данными на клиенте, изоморфность — не нужна совсем.
Все эти схемочки очень интересны конечно, но в них не учитывается один момент — запросы к АПИ/базе при рендеринге на сервере. И эти запросы (их обычно много) обычно получаются заметно медленнее чем скачивание бандла (рендеринг происходит после самого долгого из них). То есть при обычном подходе пользователь скачивает бандл, дальше у него рендерятся какие-то блоки, которые отправляют запросы за своими данными, выводят лоадер и по мере получения данных раздупляются, тут пользователь уже получает какую-то обратную связь, он видит, что что-то грузится, в случае изоморфного приложения здесь пользователь всё ещё видит белый экран. Первые полностью готовые блоки обычно получается быстрее чем рендеринг на сервере, хотя полностью готовый прил в случае с изоморфностью будет конечно раньше. В общем, без прогрессивного рендеринга вся эта изоморфность может быть интересна только для SEO (и то слишком дорогое SEO выходит, проще по старинке), в ощущаемой скорости загрузки изоморфные приложения всё же проигрывают. А понимая как работает реакт я сомневаюсь, что в него можно нормально этот прогрессивный рендеринг вкорячить, пока видел только одну попытку, и явно неудачную. Вот в этой ветке https://habrahabr.ru/post/280636/#comment_8882284 я как раз встал на сторону реакта (для выяснения истины так сказать) и получил ровно то, чего сам и придерживаюсь.
Для чего вообще нужна изоморфность?