Перевод статьи Peter Chang: Break Down Isomorphic and Universal Boilerplate: React-Redux server rendering.
Серверный рендеринг с помощью React Redux
Ищите шаблон на React-Redux? Если да, то вряд ли у вас проблемы с поиском. Потому что вы нашли ТОННЫ шаблонов. Andrew Farmer
С момента релиза React и Redux я находился на седьмом небе от счастья потому что 1) Изоморфность React даёт преимущества к скорости загрузки и SEO при серверном рендеренге и позволяет рендерить компоненты на клиенте уже после загрузки страницы и 2) Идеи Flux-архитектуры расширяют компоненты React с помощью использования однонаправленного потока данных.
Однако, после анализа тысячи шаблонов, мне пришлось выпить не одну чашку чая, чтобы успокоиться. Потому что ни один из шаблонов не оказался простым в освоении. Так что мне пришлось изучать разработку шаблонов с нуля. Эта статья представляет собой заметку, где я пояснил, как разбить шаблон на понятные части шаг за шагом. В конце концов, я выделил пять пунктов и они являются причиной такого большого разнообразия изоморфных шаблонов:
- Dev-сервер и сборка проекта
Настройка и создание среды разработки для решения таких проблем, как горячее обновление (HMR), слежение за изменением файлов, сборка бандлов и так далее. Популярные инструменты для этого: webpack, gulp, nodenpm, broserify, grunt.
-
React-Redux-компоненты и поток данных
-
Роутинг
Чтобы решить, какую иерархию роутинга использовать, нужно ответить на вопросы: это SPA? Должен ли сервер иметь CORS? Может быть стоит запускать приложение внутри express-сервера, а не на клиенте?
- Общие редюсеры/ создатели экшенов
Зависит от роутинга
- Тесты
Пока будем говорить о втором пункте, потому что он является неизменным от шаблона к шаблону. Остальные пункты меняются в зависимости от предпочтений разработчиков. SSR, общие компоненты и Redux модули — это ядро изоморфной flux-архитектуры - причина того, почему в каждом шаблоне используются пакеты react, redux и react-redux.
Склонируйте из репозитория helloWorld-пример на React-Redux. Мы будем разбивать его на части, чтобы понять, как работает каждый файл:
Серверный рендеринг с помощью React Redux
Выше представлена структура работы приложения. Приложение использует Express, который работает только с одним роутом, отображая с помощью функции res.sendFile
index.html
в браузере. Особое внимание следует уделить части, обведенной голубым контуром. Там показано взаимодействие React, Redux, корневого компонента, стора и редюсера.
React-Redux-структура данных, стора, пропсов, стейта и компонентов
Это пример, предоставленный официальной документацией, Facebook даёт некоторые советы по организации иерархии компонентов: использование «умных» и «глупых» компонентов, использование connect()
вместо store.subscribe()
и так далее.
index.js
— это вершина иерархии, корневой компонент, содержащий в себе все остальные компоненты в виде виртуального DOM, также это единственный компонент, использующий большое число зависимостей. Помимо этого, другие компоненты также включают в себя сторонние зависимости, обеспечивающие чистоту кода.
До сих пор я чувствую, как много Facebook сделал для нас, разработчиков.
Ниже расположены функции, играющие ключевую роль в React-Redux-компонентах:
Этот компонент волшебным образом делает стор доступным сразу во всех умных компонентах, не передавая его явно напрямую.
import { Provider } from 'react-redux'
let store = createStore(todoApp)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Подключает React-компонент к Redux, позволяя компоненту использовать стор верхнего уровня. При этом отпадает необходимость получать стор в виде пропсов от родителя. (взято отсюда).
Условно работу connect(...)
можно разбить на следующие стадии:
- Передача стора внутрь корневого компонента
Чтобы начать подключать компоненты, нужно обернуть корневой компонент в Provider
и передать в него переменную store
:
import helloReducer from './reducers'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
let store = createStore(helloReducer)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
- Подключение стора к умным компонентам
Документация React-Redux описывает множество способов использования connect
. Для моей цели понадобятся только mapStateToProps
и mapDispatchToProps
, чтобы покрыть всю функциональность.
-
преобразование стейта в пропсы (
mapStateToProps
) позволит компоненту Hello использоватьthis.props.message
из Redux-стора. -
преобразование диспатчинга экшенов (
mapDispatchToProps
) для HELLO_WORLD позволяет использоватьthis.props.onClick
, как функцию внутри компонента Hello.
const mapStateToProps = (state, ownProps) => {
return {
message: state.helloWorld.message
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch({ type: HELLO_WORLD })
}
}
}
const HelloWorld = connect(
mapStateToProps,
mapDispatchToProps
)(Hello)
Ниже представлены некоторые из npm модулей, о которых знают не все:
По умолчанию Redux не включен в React, поэтому пакет react-redux нужно устанавливать дополнительно. Это предполагает, что вы должны использовать сборщики модулей типа Webpack или Browserify, которые работают с CommonJS.
Это простая миддлвара для Webpack. Служит для обработки файлов, загружающихся из npm-пакетов. Используется только при разработке (узнать больше).
Миддлвара, работающая в паре с webpack-dev-middleware, для горячего обновления (HMR) webpack-бандлов на сервере (узнать больше).
Ниже раскрыты некоторые термины и концепции:
Означает горячая замена (обновление) модулей или хотрелоад. Это фишка Webpack, позволяющая обновлять ваш JavaScript-код без перезагрузки браузера (узнать больше).
Создает объект, содержащий значение нескольких редюсеров, который затем можно передать в createStore
.
- Создает Redux-стор, хранящий стейт-дерево вашего приложения.
- Создание функции
createStore(reducer, [initialState], [enhancer])
, которая затем передаётся в<Provider>
:
import { createStore, combineReducers } from 'redux'
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([ action.text ])
default:
return state
}
}
function prefixTodos(state = [], action) {
switch (action.type) {
case 'PRE_ADD_TODO':
return state.concat([ 'pre_'+action.text ])
default:
return state
}
}
const mixReducers= combineReducers({todos, prefixTodos})
let store = createStore(mixReducers, [ 'Use Redux' ])
По факту эти функции делают одно и то же в Redux, но официальная документация React НЕ советует использовать store.subscribe()
, по той причине, что в connect()
внесено множество оптимизаций, которые сложно сделать вручную, используя store.subscribe()
.
С помощью connect()
создаётся «умный» компонент, подключаемый к Redux-стору.
Подробнее о разделении на «умные» и «глупые» здесь
- «Умные» компоненты предоставляют данные для «глупых» компонентов
- «Глупые» компоненты: 1) Не имеют зависимостей от остального приложения 2) Определяют визуальную составляющую приложения.
- https://www.npmjs.com/package/redux
- http://redux.js.org/docs/introduction/Examples.html
- https://medium.com/@firasd/quick-start-tutorial-using-redux-in-react-apps-89b142d6c5c1
- http://andrewhfarmer.com/starter-project/
- https://www.codementor.io/reactjs/tutorial/redux-server-rendering-react-router-universal-web-app
- https://github.com/WilberTian/StepByStep-Redux/blob/master/06.react-redux.md
Слушайте наш подкаст в iTunes и SoundCloud, читайте нас на Medium, контрибьютьте на GitHub, общайтесь в группе Telegram, следите в Twitter и канале Telegram, рекомендуйте в VK и Facebook.