@MarkLb

Как разделять отвественность в React и как решить проблему с оповещением состояния о измении Модели?

Изучаю React, JavaScript, делаю тестовый проект - Журнал занятий в спорт-зале.

9CMLxtS.png
Древо компонентов


vUA7PqN.png


Очень толстый Exercise, он:
- Заполняет себя данными из формыhandleChange, handleSubmit)
- Принимает данные с формы о Set(handleWeightChange, handleRepsChange, toggleMaxReps)
- Управляет Моделью коллекции Set (addSet, getSet, updateSet), и им самим(changeWeight, changeReps).
- Рендерит данные

Компонент на Github: https://github.com/likont/workout-diary.loc/blob/m...
Код

import React from "react";
import generateUniqueId from "../utils/generateUniqueId";
import Set from "./Set";

class Exercise extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            id: generateUniqueId(),
            label: null,
            confirmed: false,
            sets: [],
        }

        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);

        this.handleWeightChange = this.handleWeightChange.bind(this);
        this.handleRepsChange = this.handleRepsChange.bind(this);
        this.toggleMaxReps = this.toggleMaxReps.bind(this);
    }

    handleChange(event) {
        this.setState({label: event.target.value});
    }

    handleSubmit(event) {
        this.setState({
            confirmed: true,
            sets: [{id: generateUniqueId(), weight: null, reps: null, enableMaxReps: false}]
        })
        event.preventDefault();
    }

    handleWeightChange(event, id) {
        this.updateSet({
            id,
            updatedSet: {weight: event.target.value},
            listener: (sets) => this.addSetWhenPrevSetFilled(sets)
        });
    }

    handleRepsChange(event, id) {
        this.updateSet({
            id,
            updatedSet: {reps: event.target.value},
            listener: (sets) => this.addSetWhenPrevSetFilled(sets)
        });
    }

    toggleMaxReps(id) {
        let set = this.getSet(id);
        if (set) {
            set.enableMaxReps = !set.enableMaxReps;
            this.updateSet({id, updatedSet: set});
        }
    }

    addSetWhenPrevSetFilled(sets) {
        let lastSet = sets[sets.length - 1];
        let isNeedEmptySet = true;

        for (let key in lastSet) {
            if (lastSet[key] === null) {
                isNeedEmptySet = false;
            }
        }

        if (isNeedEmptySet) {
            this.addSet({weight: null, reps: null});
        }
    }

    addSet({weight, reps, enableMaxReps}) {
        enableMaxReps = enableMaxReps || false;
        this.setState({
            ...this.state,
            sets: [
                ...this.state.sets,
                {id: generateUniqueId(), weight, reps, enableMaxReps}
            ]
        });
    }

    getSet(id) {
        for (let set of this.state.sets) {
            if (set.id === id) {
                return set;
            }
        }

        return null;
    }

    updateSet({id, updatedSet, listener}) {
        updatedSet = updatedSet || {};
        this.setState({
            ...this.state,
            sets: this.state.sets.map((set) => {
                if (set.id === id) {
                    return {
                        ...set,
                        ...updatedSet
                    };
                }
                return set;
            })
        });

        if (typeof listener === "function") {
            setTimeout(() => {
                listener(this.state.sets);
            }, 1000)

        }
    }

    render() {
        return (
            this.state.confirmed ?
                <ExerciseView
                    id={this.state.id}
                    title={this.state.label}
                    sets={this.state.sets}
                    changeReps={this.handleRepsChange}
                    changeWeight={this.handleWeightChange}
                    toggleMaxReps={this.toggleMaxReps}
                /> :
                <ExerciseFormView onSubmit={(e) => this.handleSubmit(e)} onChange={(e) => this.handleChange(e)}/>
        );
    }

}

function ExerciseView({id, title, sets, changeWeight, toggleMaxReps, changeReps}) {
    return (
        <div className="col-12" key={id}>
            <h5>{title}</h5>
            <table className="table table-bordered">
                <tbody>
                {sets.map((set, index) =>
                    <Set
                        set={set}
                        changeWeight={(e) => changeWeight(e, set.id)}
                        changeReps={(e) => changeReps(e, set.id)}
                        toggleMaxReps={() => toggleMaxReps(set.id)}
                        number={index + 1}
                    />)}
                </tbody>
            </table>
        </div>
    );
}

function ExerciseFormView({onSubmit, onChange}) {
    return (
        <div className="col-12">
            <form onSubmit={onSubmit}>
                <div className="input-group mt-4 mb-4">
                    <select className="custom-select" id="select-exercise" onChange={onChange}
                            aria-label="Select workout type">
                        <option defaultValue>Выбрать...</option>
                        <option value="Жим лежа">Жим лежа</option>
                        <option value="Отжимания">Отжимания</option>
                        <option value="Тяга гантели">Тяга гантели</option>
                    </select>
                    <div className="input-group-append">
                        <button className="btn btn-outline-secondary" type="submit">Начать</button>
                    </div>
                </div>
            </form>
        </div>
    );
}

export default Exercise;



Так в чём проблема?
1. Это похоже на нарушение Приницпа единой отвественности.
2. Не нравится, что Set - это Plain Object, надо помнить какие свойства он содержит.
3. Слишком длинный код.

Как можно решить проблему?

Какое решение вижу?
1) Создать классы для Моделей:
- SetCollection- управляет массивом подходов(SetCollection.add(), SetCollection.remove())
- Set- управляет своими данными (Set.create(), Set.changeMaxWeght(), др.)

Проблема: Классовые объекты вне Component State => React не узнает о их изменениях => React не рендерит страницу.
Разве что передавать замыкание с forceUpdate() в конструктор классов SetCollection, Setи при изменения данных - вызывать его.

2) Создать компонент Set, управление его формой(функции) проводить там.

3) Компонент = Controller. Передает данные с формы в Модель, а та если меняет себя - оповещает компонент, и он производит перерендеринг.

Но наверняка, есть какой-то стандарт, которым можно решить мою проблему, но я его не смог найти.
  • Вопрос задан
  • 67 просмотров
Пригласить эксперта
Ответы на вопрос 1
@Yury093
Для управлением состоянием нужно использовать одну из библиотек для управления состоянием.
Одна из самых популярных сейчас - Redux. Есть еще Mobx, например. Есть и другие новые модные - почитайте сравнение по запросу "alternatives to Redux" ну или вот статья: https://habr.com/ru/company/ruvds/blog/566102/
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы