Время прочтения: 6 мин.

Привет! В предыдущей статье (ссылка) мы с вами заложили первый кирпичик для разработки нашего сервиса. Теперь давайте создадим структуру приложения.

В папке src создадим папки actions, components, reducers, services и файл store.js. Последний нам понадобится для использования redux.

В папке components мы будем размещать все компоненты нашего сервиса. Создадим там папку app и в неё перенесем следующие файлы:

  1. app.js
  2. 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 и ждем завершения установки:

  1. npm install redux
  2. npm install react-redux
  3. 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 на нашу функцию из storechangeParams.

Должен получиться вот такой компонент:

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 проект создан. В следующей статье мы попробуем навесить стили и подключим сервис к таблицам для сохранения наших данных.