Изучаю React, JavaScript, делаю тестовый проект - Журнал занятий в спорт-зале.
Очень толстый 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. Передает данные с формы в Модель, а та если меняет себя - оповещает компонент, и он производит перерендеринг.
Но наверняка, есть какой-то стандарт, которым можно решить мою проблему, но я его не смог найти.