Знакомство с React.js, React Router, Redux, Webpack за 7 шагов

Александр Зайцев
10 min readJun 7, 2019

--

Помните, ключевой способ достичь успеха — это метод проб и ошибок. (с) Томас Эдисон

Для кого эта статья?

Эта статья — руководство для тех, кто уже знаком с JavaScript. Однако глубокое знание языка не является обязательным условием для её прочтения. С помощью этой статьи вы сможете собрать каркас будущего приложения и использовать его в своих проектах.

Шаг 1 — package.json

package.json

Для начала потребуется создать папку проекта — mkdir <название_проекта> (mkdir lesson). Переходим в неё с помощью команды cd /название_диска/название_проекта/ (cd /c/lesson).

Далее вводим команду npm init (автоматически создается файл package.json).

Отвечаем на вопросы:

package name  —  по умолчанию название корневой папки, нажимаем Enter;
version  —  версия приложения, нажимаем Enter;
description  —  описание приложения;
entry point  —  входной файл по умолчанию index.js, нажимаем Enter;
test command  —  тестирование сборки, нажимаем Enter;
git repository  —  ссылка на ваш репозиторий;
keywords  —  ключевые слова для поиска приложения;
author  —  имя автора или команды;
license  — (ISC) - свободная лицензия для ПО, нажимаем Enter;
Is this OK? (yes)- вводим yes, и package.json создан.

Шаг 2 — Установка React.js

Заходим в корень проекта cd /имя_диска/название_проекта и в командной строке вводим npm install --save react (появится папка node_modules и файл package-lock.json).

node_modules  —  в нем содержатся все установленные пакеты;
package-lock.json используется для блокировки зависимостей от определенного номера версии.

Ура! React.js установлен))
Документация ReactJS

Шаг 3 — .gitignore

.gitignore

Создаем файл с названием .gitignore в корне проекта.
Внутри указываем список папок, файлов, которые не должны попасть на git.

/node_modules
/dist — production сборка
/.idea — указать, если используете WebStorm

Шаг 4 — Установка Webpack, Babel

Заходим в корень проекта cd /название_диска/название_проекта/ и устанавливаем необходимые пакеты:

npm install --save-dev webpack webpack-cli webpack-dev-server html-webpack-plugin @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/preset-react @babel/runtime babel-loader babel-plugin-transform-class-propertieswebpack-cli  —  https://www.npmjs.com/package/webpack-cli ;
webpack-dev-server  —  сервер Node.js;
html-webpack-plugin упрощает создание HTML-файлов;
babel  —  JavaScript-компилятор.

В корне проекта создаем файл webpack.config.js (пример кода), читаем комментарии. Если будете копировать, то комментарии нужно удалить.

Также в корне проекта создаем файл .babelrc.

.babelrc 
________
{
"presets": ["@ babel / preset-env", "@ babel / preset-response"],
"plugins": ["transform-class-properties", "@ babel / plugin-transform-runtime"]
}
// Комментарии не копируем
// Используем @babel/preset-env для того, чтобы применять только те плагины, которые необходимы для нашего проекта;
// @babel/preset-react преобразовывает в JavaScript и JSX-код;
// transform-class-properties  —  https://www.npmjs.com/package/babel-plugin-transform-class-properties ;
// @babel/plugin-transform-runtime  —  плагин, который позволяет повторно использовать внедренный вспомогательный код Babel
для экономии на размере кода.
код GitHub

В package.json, в блоке script: {} добавим 2 строчки:

"start": "webpack-dev-server --mode development --open --hot --progress --colors --config ./webpack.config.js", 
// запуск нашего приложения;
"build": "webpack --mode production"
// production сборка

Webpack для новичков (автор — Саша Азарова)

Шаг 5 — Создание корневого каталога и запуск приложения

Заходим в корень проекта cd /название_диска/название_проекта.

Создаем папку mkdir public и переходим в неё cd /public, внутри создаем файл index.html (печатаем !, нажимаем Tab). Открываем файл index.html, затем внутри тега <body> добавляем <div id="root"></div> (пример кода).

В корне проекта создаем папку mkdir src и переходим в неё cd src.

Внутри создаем папку mkdir components и переходим в неё cd components .

Внутри создаем папку mkdir App и переходим в неё cd App , внутри создаем файл index.js.

src > components > App > index.js 
_________________________________
import React, { Component } from "react";export default class App extends Component {
render () {
const {children} = this.props;
return (
<div>
{children}
</ div>
);
}
}
код github

В корне папки src создаем входной файл index.js, код.

src > index.js 
______________
import React from "react";
import { render } from 'react-dom';
import App from './components/App';
render (
<App>
Hello World!
</ App>,
document.getElementById ("root")
);
код github

Запускаем приложение командой npm start, получаем ошибку. Устанавливаем пакет react-dom npm install --save-dev react-dom и снова запускаем приложение.

react-dom предоставляет методы, специфичные для DOM, которые можно использовать на верхнем уровне вашего приложения и в качестве аварийного люка, чтобы выйти за пределы модели React, если это необходимо.

Шаг 6 — React Router

Заходим в корень проекта cd /название_диска/название_проекта/ и устанавливаем необходимый пакет:

установка npm --save-dev реагирует-маршрутизатор-домreact-router-dom — роутинг для приложения.

Переходим в папку сomponents cd src/components, затем внутри создаем следующие папки mkdir Header Main Home Roster.

Header  —  в ней мы будем хранить основную навигацию для нашего приложения; 
Main  —  в ней мы будем хранить группу роутов;
Home и Roster  —  в них будет информация.

Внутри каждой папки создаем файл index.js.

Header > index.js

/*...*/
import { Link } from "react-router-dom";
/*...*/
<Link to="/">Home</Link>
/*...*/
полный код на github

Для перехода между компонентами будем использовать <Link to="/">, а не <a href=""/>.
Почему?
<a href=""/> — перезагружает страницу полностью, что нам не подходит. Необходимо перерисовать (перерендерить) только вызываемый компонент, поэтому мы будем использовать <Link to="/">.
<Link to="/"> — не перерисовывает и не перезагружает страницу полностью, а перерисовывает только вызванный компонент.

Main > index.js

/*...*/
import { Switch, Route } from "react-router-dom";
/*...*/
<Switch>
<Route exact path='/' component={Home}/>
/*...*/
</Switch>
/*...*/
полный код на github

<Switch /> — используется для группировки роутов;
<Route path='/' component={/*...*/} /> — рендер пользовательского интерфейса, который мы передаем через component={название_компонента_который_необходимо_рендерить}.

Home > index.js

import React, { Fragment } from "react"; const Home = () => <Fragment>Home</Fragment>; export default Home;код на github

Roster > index.js

import React, { Fragment } from "react"; const Roster = () => <Fragment>Roster</Fragment>; export default Roster;код на github

Переходим в папку cd src, открываем файл index.js:

/*...*/
import {BrowserRouter as Router} from "react-router-dom";
render(
<Router>
<App>
Hello World!
</App>
</Router>,
document.getElementById("root")
);

HashRouter используем, когда у нас статический веб-сайт;
BrowserRouter следует использовать, когда обрабатываются динамические запросы на сервере.

Переходим в папку cd components/App, открываем файл index.js

/*...*/
import Header from "../Header";
import Main from "../Main";
/*...*/
<Header />
<Main />
/*...*/
полный код на github

Запускаем приложение командой npm start, теперь мы можем вызывать разные компоненты по нажатию на ссылки Home или Roster.

Есть маленький нюанс: если обновить страницу, например, страницу /roster, будет получена ошибка cannot GET, которую легко исправить.
Открываем в корне проекта файл webpack.config.js, находим на 25 строчке devServer, добавляем historyApiFallback: true и проверяем в браузере.

webpack.config.js/*...*/
devServer: {
historyApiFallback: true,
/*...*/
}
/*...*/
полный код на github

Документация React Router

Шаг 7 — Redux

Заходим в корень проекта cd /название_диска/название_проекта/ и устанавливаем необходимые пакеты:

npm install --save redux react-redux redux-thunk redux-logger connected-react-router historyredux  —  библиотека состояния для вашего приложения;
react-redux  —  официальная версия redux для react;
redux-thunk  —  позволяет писать создатели действий, которые возвращают функцию вместо самого действия;
redux-logger  —  библиотека, которая показывает все действия приложения, связанные с redux, и выводит их на консоль;
connected-react-router  —  библиотека, которая синхронизирует маршруты с состоянием;
history  —  библиотека, которая позволяет легко управлять историей сеансов.

Есть несколько основных принципов в Redux, которые нужно понимать:

Есть 1 глобальный объект состояния, который управляет состоянием всего приложения. В этом примере он будет идентичен начальному состоянию компонента. Это единственный источник истины.

Единственный способ изменить состояние — это отправка действий. Действия представляют собой объекты описывающие то, что должно измениться. Создатели действий (Action Creators) — это функции, которые могут быть отправлены (dispatched). Всё что они делают это возвращают действие.

Когда действие отправлено, редьюсер (функция) либо изменяет состояние в соответствии с отправленным действием, либо возвращает текущее состояние если действие не применимо к редьюсеру.

Редьюсеры — это “чистые функции”. Они не должны изменять состояние, вместо этого они должны возвращать модифицированную копию.

Индивидуальные редьюсеры объединены в один корневой редьюсер (rootReducer) для создания отдельных свойств состояния.

Хранилище (store) — это такая штука, которая всё это объединяет: оно представляет состояние используя корневой редьюсер, какие-то промежуточные слои (middleware, в нашем случае Thunk), и позволяет фактически отправлять действия.

Для того чтобы использовать Redux вместе с React существует компонент , который оборачивает всё наше приложение и передаёт хранилище store всем дочерним элементам.

Переходим в папку cd /src и создаем папку mkdir redux. Переходим в неё cd /redux и создаем следующие папки mkdir actions reducers stores .

actions

В actions мы будем хранить действия. Переходим в папку actions и создаем файл index.js, в котором будем перечислять действия.

actions > index.js
__________________
import { getRoster } from "./rosterActions";
// импортируем действие, а также другие ниже
export { getRoster };
// перечисляем действия и теперь,
// когда нам нужно будет вызвать действие внутри компонента,
// делаем это так:
// import { getRoster, /*и др.*/ } from "../../redux/actions";
код на github

Создаем файл действия rosterActions.js.

actions > rosterActions.js
__________________________
export const getRoster = () => dispatch => {
dispatch({ type: "GET_ROSTER_REQ" });
};
код на github

reducers

В reducers будем хранить “чистые функции”, которые вернут измененное состояние. Переходим в папку reducers и создаем файл index.js, в котором будем комбинировать/объединять редьюсеры;

reducers > index.js
___________________
import { combineReducers } from "redux";
import { connectRouter } from "connected-react-router";
import roster from "./roster";
export default history =>
combineReducers({
router: connectRouter(history),
roster
});
код на github

Создаем наш первый редьюсер roster.js.

reducers > roster.js
____________________
const initialState = {
rosterLoader: false
};
const roster = (state = initialState, action) => {
if (action.type === "GET_ROSTER_REQ") {
return {
...state,
rosterLoader: !state.rosterLoader
};
}
return state;
};
export default roster;код на github

stores

В stores храним настройки хранилища (store). Переходим в папку stores и создаем файл configureStore.js — это хранилище.

stores > configureStore.js
__________________________
import { createBrowserHistory } from "history";
import { createStore, applyMiddleware } from "redux";
import { createLogger } from "redux-logger";
import thunk from "redux-thunk";
import { routerMiddleware } from "connected-react-router";
import rootReducer from "../reducers";
const logger = createLogger();export const history = createBrowserHistory();
const createStoreWithMiddleware = applyMiddleware(
thunk,
routerMiddleware(history),
logger)
(createStore);
export default function configureStore(initialState) {
return createStoreWithMiddleware(rootReducer(history), initialState);
}
код на github

Переходим в папку src и открываем файл index.js

  1. Импортируем компонент <Provider />, он содержится в react-redux. Provider принимает свойство store, в которое мы передаем хранилище configureStore.js, и затем все дочерние элементы смогут его использовать.
  2. history поможет получить историю сеансов.

Для того, чтобы синхронизировать историю сеансов и состояние, нам нужно Router заменить на ConnectedRouterimport { ConnectedRouter } from "connected-react-router";.

src > index.js
______________
/*...*/
import { Provider } from "react-redux";
import configureStore, { history } from "./redux/stores/configureStore";
/*...*/
import { ConnectedRouter } from "connected-react-router";
const store = configureStore(); render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById("root")
);
код на github

Также изменяем наш компонент Roster, переходим в cd /components/Roster/index.js.

src > components > Roster > index.js
____________________________________
import React from "react";
import { connect } from 'react-redux';
import { getRoster } from "../../redux/actions";
import Roster from "./presenter";
const mapStateToProps = state => {
return {
state,
roster: state.roster.rosterLoader
};
};
const mapDispatchToProps = dispatch => ({
getRoster: () => dispatch(getRoster()),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Roster);
код на github

Создаем в корне Roster новый файл presenter.js.

src > components > Roster > presenter.js
________________________________________
import React, { Component, Fragment } from "react";class Roster extends Component {
render () {
const {roster, getRoster} = this.props;
return (
<Fragment>
Roster
<button onClick = {getRoster}>
{roster ? "true": "false"}
</ button>
</ Fragment>
);
}
}
export default Roster;код github

Пересобираем проект и смотрим, что получилось.

В консоли будут отображаться действия после нажатия на кнопку.
У переменной roster после каждого нажатия будет изменяться значение. Засчет этого, можно контролировать поведение элементов, обращения к api и многое другое.

Переменная доступна везде и хранит измененное состояние вплоть до полной перезагрузки страницы, именно поэтому мы и использовали для переходов между компонентами компонент <Link to=""/>, а не тег ссылки <a href=""/>.

Например, передадим переменную roster не только в компонент Roster, но и в компонент Home. Для того, чтобы передать переменную, необходимо модифицировать компонент Home, так же, как мы это сделали с компонентом Roster.

Добавим точно такое же условие и заменим получаемые значения ‘true’ на ‘Home’ и ‘false’ на ‘No Home’, соответственно. Когда будет нажата кнопка в компоненте Roster, и ‘true’ сменится на ‘false’, перейдем на страницу компонента Home и увидим, что ‘Home’ сменилось на ‘No Home’.

Официальная документация Redux
Перевод официальной документации по Redux

Когда мы создавали файл .gitignore, то указывали папку /dist. Она создается, если у нас есть production сборка.

После ввода команды npm run build начинается компиляция, и в структуре проекта появляется новая папка с названием /dist.

Данная сборка запускается только на сервере. Чтобы проверить локально, установим пакет serve глобально npm install serve -g. Переходим в папку cd dist и вводим команду serve. Затем переходим по ссылке, которую предоставит пакет serve. Готово! Теперь у вас есть production сборка!

Успех составляет только 1%, а 99% — неудачи.(с) Соитиро Хонда

--

--