Время прочтения: 6 мин.
Привет! В предыдущей статье (ссылка) мы с вами заложили первый кирпичик для разработки нашего сервиса. Теперь давайте создадим структуру приложения.
В папке src создадим папки actions, components, reducers, services и файл store.js. Последний нам понадобится для использования redux.
В папке components мы будем размещать все компоненты нашего сервиса. Создадим там папку app и в неё перенесем следующие файлы:
- app.js
- app.css
В файле app.js будет размещаться код компонента, а в app.css расположатся стили компонента.
Также нам понадобится index.js. Он нужен для удобства импорта этого компонента, и будет содержать всего две строки:
import App from './app';
export default App;
Теперь в файле index.js в папке src мы изменим строку импорта компонента App.
import App from ‘./app’;
меняем на
import App from './components/app';
Теперь попробуем запустить проект, чтобы посмотреть не сделали ли мы ошибку.
Необходимо запустить командную строку. Перейти в папку нашего проекта и ввести команду npmstart.
Должна открыться пустая страница проекта.
Теперь добавим нашему элементу функциональности. Добавим следующий код в компонент App внутрь тега div:
<div>
<label>Имя: <input type='text' /></label>
</div>
<div style={{ width: '10%' }}>
<label>Сила: <input type='number' /></label>
<label>Ловкость: <input type='number' /></label>
<label>Телосложение: <input type='number' /></label>
<label>Интеллект: <input type='number' /></label>
<label>Мудрость: <input type='number' /></label>
<label>Харизма: <input type='number' /></label>
</div>
У нас появились поля ввода характеристик для нашего героя.
Чтобы хранить значения, нам необходимо немного изменить элемент, сделав его не функцией, а классом. Для этого импортируем из библиотеки react Component:
import React, { Component } from 'react';
Затем модифицируем элемент:
export default class App extends Component {...}
И удалим следующую строку:
export default App;
Отлично! Теперь App подготовлен для дальнейших манипуляций.
Создадим state, в котором будет храниться состояние объекта. Внутри нашего класса пишем следующее:
state = {
name: '',
strength: 0,
dexterity: 0,
constitution: 0,
intelligence: 0,
wisdom: 0,
charisma: 0
}
Напишем обработчик событий для полей. Он располагается тоже внутри класса:
onChangeInput(changeInput, ev) {
const val = ev.target.value;
this.setState((state) => {
return { ...state, [changeInput]: val }
})
}
ev – это событие, которое мы получим от input. changeInput – имя поля, значение которого мы изменяем. setState – функция, которая изменяет state. Так как менять состояние напрямую нельзя, мы возвращаем копию состояния (которую получаем с помощью spread-оператора) и новое значение поля.
В каждом поле ввода перед закрывающим слешем добавим:
onChange={(ev) => this.onChangeInput('name', ev)}
value={this.state.name }
onChange — обработчик события будет вызывать функцию onChangeInput, и передавать ей само событие и имя поля которое изменяем в state.
Подсказка: для поля strength в state мы передаем ‘strength’ и т.д.
Теперь, при любом изменении любого поля изменения попадают в наш локальный state.
Давайте проверим, что все именно так. Используем метод жизненного цикла компонента componentDidUpdate(). Данный метод вызывается, когда компонент должен обновиться.
Напишем его внутри нашего класса:
componentDidUpdate() {
console.log(this.state)
}
Открываем браузер и видим, что при каждом изменении любого поля в консоль выводится текущее состояние state.
Теперь приступим к установке библиотек для redux. Переходим в командную строку нажимаем Ctrl+C, Y и Enter.
Пишем следующие строки. После каждой нажимаем Enter и ждем завершения установки:
- npm install redux
- npm install react-redux
- npm install redux-thunk
Теперь у нас установлены все библиотеки для redux, которые понадобятся в этом проекте.
Немножко теории — как работает связка react+redux.
У нашего приложения есть только один источник данных – это store. Store содержит в себе общий state, изменение которого происходит через reducer’ы. Например, если пользователь вводит что-нибудь в наше поле ввода, функция onChange вызывает reducer, который меняет общий state и компонент перерисовывается в связи с изменением state.
Теперь напишем простой reducer, который будет получать имя поля, и значение, которое ввел пользователь.
В файле reducers введем следующее
const initialState = {
name: '',
strength: 0,
dexterity: 0,
constitution: 0,
intelligence: 0,
wisdom: 0,
charisma: 0
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'CHANGE_PARAMS': {
return {
...state,
[action.payload.type]: action.payload.value
}
}
}
}
initialState – начальное состояние нашего приложения, reducer – чистая функция, которая принимает state и action и возвращает новый state.
В action у нас содержится type – это тип нашего действия, в payload содержится значение, которое мы добавляем в наш state.
Перейдем в папку actions – там будут action-creators которые будут создавать объекты, которые мы будем передавать в reducers.
Создадим файл index.js и напишем следующее:
const changeParams = (type, value) => {
return {
type: 'CHANGE_PARAMS',
payload: {
type,
value
}
}
}
export { changeParams };
Давайте вынесем нашу форму в отдельный компонент Form. Для этого в папке components создадим новую папку form и в ней разместим файлы form.js и index.js
В index.js пишем
import Form from './form'
export default Form;
В файл form.js перенесем все, что относится к нашему компоненту, а также импортируем библиотеку react.
import React, { Component } from 'react';
export default class Form extends Component {
componentDidUpdate() {
console.log(this.state)
}
onChangeInput(changeInput, ev) {
const val = ev.target.value;
this.setState((state) => {
return { ...state, [changeInput]: val }
})
}
render() {
return (
<div>
<div>
<label>Имя: <input type='text' value={this.state.name} onChange={(ev) => this.state.onChangeInput('name', ev)} /></label>
</div>
<div style={{ width: '10%' }}>
<label>Сила: <input type='number' value={this.state.strength} onChange={(ev) => this.state.onChangeInput('strength', ev)} /></label>
<label>Ловкость: <input type='number' value={this.state.dexterity} onChange={(ev) => this.state.onChangeInput('dexterity', ev)} /></label>
<label>Телосложение: <input type='number' value={this.state.constitution} onChange={(ev) => this.state.onChangeInput('constitution', ev)} /></label>
<label>Интеллект: <input type='number' value={this.state.intelligence} onChange={(ev) => this.state.onChangeInput('intelligence', ev)} /></label>
<label>Мудрость: <input type='number' value={this.state.wisdom} onChange={(ev) => this.state.onChangeInput('wisdom', ev)} /></label>
<label>Харизма: <input type='number' value={this.state.charisma} onChange={(ev) => this.state.onChangeInput('charisma', ev)} /></label>
</div>
</div>
)
}
}
State в данном компоненте нам больше не нужен – мы будем получать его из store.
Теперь создадим наш store.
Переходим в папку src, открываем файл store.js и пишем следующее
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import reducer from './reducers';
const store = createStore(reducer, applyMiddleware(thunkMiddleware));
export default store;
Вернемся в файл app.js и импортируем пару необходимых библиотек:
import { Provider } from 'react-redux';
import store from '../../store';
а также Form из form.js и store из store.js:
import Form from '../form'
import store from '../../store';
Обернем элемент Form в Provider, а в качестве параметра передадим в Provider store. В итоге элемент app.js будет выглядеть следующим образом.
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import Form from '../form'
import store from '../../store';
import './app.css';
export default class App extends Component {
render() {
return (
<div>
<Provider store={store}>
<Form/>
</Provider>
</div>
);
};
}
В файле form.js необходимо подключить store, который provider передает всем компонентам внутри себя.
Импортируем функцию connect из react-redux и changeParams из actions.
import { connect } from 'react-redux';
import { changeParams } from '../../actions'
Так как мы будем применять функцию connect к нашему компоненту уберем export default из объявления класса и перенесем данный export вниз.
export default Form;
Добавим функцию connect для подключения store. Данная функция принимает в себя две функции mapStateToProps и mapDispatchToProps и выполняется относительно Form. Таким образом наш export примет вид
export default connect(mapStateToProps, mapDispatchToProps)(Form);
С помощью mapStateToProps мы получаем значение store, а с помощью mapDispatchToProps получаем возможность на него воздействовать через action-creator. Давайте напишем эти функции.
const mapStateToProps = (state) => {
return {
params: state
}
}
const mapDispatchToProps = (dispatch) => {
return {
changeParams: (nameField, type) => {
dispatch(changeParams(nameField, type))
}
}
}
Удаляем функцию onChangeInput – она нам больше не нужна.
Во всех полях и в функции componentDidUpdate заменим this.state на this.props.params.
А также заменим функцию onChangeInput в input на нашу функцию из store – changeParams.
Должен получиться вот такой компонент:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { changeParams } from '../../actions'
class Form extends Component {
componentDidUpdate() {
console.log(this.props.params)
}
render() {
return (
<div>
<div>
<label>Имя: <input type='text' value={this.props.params.name} onChange={(ev) => this.props.changeParams('name', ev.target.value)} /></label>
</div>
<div style={{ width: '10%' }}>
<label>Сила: <input type='number' value={this.props.params.strength} onChange={(ev) => this.props.changeParams('strength', ev.target.value)} /></label>
<label>Ловкость: <input type='number' value={this.props.params.dexterity} onChange={(ev) => this.props.changeParams('dexterity', ev.target.value)} /></label>
<label>Телосложение: <input type='number' value={this.props.params.constitution} onChange={(ev) => this.props.changeParams('constitution', ev.target.value)} /></label>
<label>Интеллект: <input type='number' value={this.props.params.intelligence} onChange={(ev) => this.props.changeParams('intelligence', ev.target.value)} /></label>
<label>Мудрость: <input type='number' value={this.props.params.wisdom} onChange={(ev) => this.props.changeParams('wisdom', ev.target.value)} /></label>
<label>Харизма: <input type='number' value={this.props.params.charisma} onChange={(ev) => this.props.changeParams('charisma', ev.target.value)} /></label>
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
params: state
}
}
const mapDispatchToProps = (dispatch) => {
return {
changeParams: (nameField, type) => {
dispatch(changeParams(nameField, type))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Form);
Теперь, если мы все сделали правильно у нас должно получиться следующее.
111
Поздравляю, наш минимальный react-redux проект создан. В следующей статье мы попробуем навесить стили и подключим сервис к таблицам для сохранения наших данных.