Not so long ago, React positioned itself as "V in MVC". After this commit, the marketing text changed, but the essence remained the same: React is responsible for the display, the developer - for everything else, that is, speaking in MVC terms, for Model and Controller.
One of the solutions for managing the Model (state) of your application is Redux. Its appearance is motivated by the increased complexity of frontend applications that MVC cannot handle.
Main Technical Imperative of Software Development - complexity management
- Perfect code
Redux offers to manage complexity with predictable state changes. Predictability is achieved through three fundamental principles :
Was Redux able to overcome the increased complexity and was there anything to fight with?
Redux is inspired by Flux , a solution from Facebook. The reason for the creation of Flux, as stated by Facebook developers ( video ), was the problem of the scalability of the MVC architectural pattern.
According to the description of Facebook, the connections of objects in large projects using MVC eventually become unpredictable:
The problem of the unpredictability of changes in MVC is also written in Redux's motivation. The picture below illustrates how Facebook developers see this problem.
Flux, in contrast to the described MVC, offers a clear and coherent model:
In addition, using Flux, several Views can subscribe to the Stores of interest and be updated only when something changes in these Stores. This approach reduces the number of dependencies and simplifies development.
The MVC implementation from Facebook is completely different from the original MVC, which was widely distributed in the Smalltalk world. This difference is the main reason for the statement "MVC does not scale."
MVC is the primary approach to developing user interfaces in Smalltalk-80. Like Flux and Redux, MVC was created to reduce software complexity and speed development. I will give a brief description of the basic principles of the MVC approach, a more detailed overview can be found here and here .
Responsibilities of MVC entities:
And now that Facebook missed, realizing MVC - the connections between these entities:
Look at the image below. The arrows directed from Model to Controller and View are not attempts to change their state, but alerts about changes in Model.
The original MVC is completely different from the Facebook implementation, in which the View can change the Model set, the Model can change the View set, and the Controller does not form a close one-to-one relationship with the View. Moreover, Flux is MVC, in which Dispatcher and Store play the role of Model, and Action is sent instead of calling methods.
Let's look at the code for a simple React component:
class ExampleButton extends React.Component {
render() { return (
<button onClick={() => console.log("clicked!")}>
Click Me!
</button>
); }
}
Controller'a MVC:
Controller , , View Model
ontroller View
, Controller View ? :
onClick={() => console.log("clicked!")}
Controller, . JavaScript , . React- View, View-Controller.
React, Model. React- Model .
React-, BaseView, props Model:
// src/Base/BaseView.tsx
import * as React from "react";
import BaseModel from "./BaseModel";
export default class <Model extends BaseModel, Props> extends React.Component<Props & {model: Model}, {}> {
protected model: Model;
constructor(props: any) {
super(props);
this.model = props.model
}
componentWillMount() { this.model.subscribe(this); }
componentWillUnmount() { this.model.unsubscribe(this); }
}
state , . View this.forceUpdate()
, . , , , .
BaseModel, , , :
// src/Base/BaseModel.ts
export default class {
protected views: React.Component[] = [];
subscribe(view: React.Component) {
this.views.push(view);
view.forceUpdate();
}
unsubscribe(view: React.Component) {
this.views = this.views.filter((item: React.Component) => item !== view);
}
protected updateViews() {
this.views.forEach((view: React.Component) => view.forceUpdate())
}
}
TodoMVC , Github.
TodoMVC , . : " ", " ", " ". . :
// src/TodoList/TodoListModel.ts
import BaseModel from "../Base/BaseModel";
import TodoItemModel from "../TodoItem/TodoItemModel";
export default class extends BaseModel {
private allItems: TodoItemModel[] = [];
private mode: string = "all";
constructor(items: string[]) {
super();
items.forEach((text: string) => this.addTodo(text));
}
addTodo(text: string) {
this.allItems.push(new TodoItemModel(this.allItems.length, text, this));
this.updateViews();
}
removeTodo(todo: TodoItemModel) {
this.allItems = this.allItems.filter((item: TodoItemModel) => item !== todo);
this.updateViews();
}
todoUpdated() { this.updateViews(); }
showAll() { this.mode = "all"; this.updateViews(); }
showOnlyActive() { this.mode = "active"; this.updateViews(); }
showOnlyCompleted() { this.mode = "completed"; this.updateViews(); }
get shownItems() {
if (this.mode === "active") { return this.onlyActiveItems; }
if (this.mode === "completed") { return this.onlyCompletedItems; }
return this.allItems;
}
get onlyActiveItems() {
return this.allItems.filter((item: TodoItemModel) => item.isActive());
}
get onlyCompletedItems() {
return this.allItems.filter((item: TodoItemModel) => item.isCompleted());
}
}
. , , . :
// src/TodoItem/TodoItemModel.ts
import BaseModel from "../Base/BaseModel";
import TodoListModel from "../TodoList/TodoListModel";
export default class extends BaseModel {
private completed: boolean = false;
private todoList?: TodoListModel;
id: number;
text: string = "";
constructor(id: number, text: string, todoList?: TodoListModel) {
super();
this.id = id;
this.text = text;
this.todoList = todoList;
}
switchStatus() {
this.completed = !this.completed
this.todoList ? this.todoList.todoUpdated() : this.updateViews();
}
isActive() { return !this.completed; }
isCompleted() { return this.completed; }
remove() { this.todoList && this.todoList.removeTodo(this) }
}
View, Model. View :
// src/TodoList/TodoListInputView.tsx
import * as React from "react";
import BaseView from "../Base/BaseView";
import TodoListModel from "./TodoListModel";
export default class extends BaseView<TodoListModel, {}> {
render() { return (
<input
type="text"
className="new-todo"
placeholder="What needs to be done?"
onKeyDown={(e: any) => {
const enterPressed = e.which === 13;
if (enterPressed) {
this.model.addTodo(e.target.value);
e.target.value = "";
}
}}
/>
); }
}
View, , Controller (props onKeyDown) Model View, Model . props' , .
View TodoListModel, :
// src/TodoList/TodoListView.tsx
import * as React from "react";
import BaseView from "../Base/BaseView";
import TodoListModel from "./TodoListModel";
import TodoItemModel from "../TodoItem/TodoItemModel";
import TodoItemView from "../TodoItem/TodoItemView";
export default class extends BaseView<TodoListModel, {}> {
render() { return (
<ul className="todo-list">
{this.model.shownItems.map((item: TodoItemModel) => <TodoItemView model={item} key={item.id}/>)}
</ul>
); }
}
View , TodoItemModel:
// src/TodoItem/TodoItemView.jsx
import * as React from "react";
import BaseView from "../Base/BaseView";
import TodoItemModel from "./TodoItemModel";
export default class extends BaseView<TodoItemModel, {}> {
render() { return (
<li className={this.model.isCompleted() ? "completed" : ""}>
<div className="view">
<input
type="checkbox"
className="toggle"
checked={this.model.isCompleted()}
onChange={() => this.model.switchStatus()}
/>
<label>{this.model.text}</label>
<button className="destroy" onClick={() => this.model.remove()}/>
</div>
</li>
); }
}
TodoMVC . , 60 . : Model View, . props', . Container-.
, Redux , , Redux . frontend- :
Redux , .
Redux , , , . Redux indirection , Presentation Components , Action' State, props. indirection' . , .
indirection' TodoMVC, Redux. State callback' onSave, ?
hadleSave
TodoItem
props onSave
TodoTextInput
onSave
Enter
, props newTodo
, onBlur
hadleSave
props deleteTodo
, , props editTodo
deleteTodo
editTodo
TodoItem
MainSection
MainSection
props' deleteTodo
editTodo
TodoItem
MainSection
App
bindActionCreator
, action' src/actions/index.js
, src/reducers/todos.js
, callback', props', 2 . , .
Flux Redux MVC, , MVC. Redux , callback' props' , . frontend-, Flux Redux, . . Facebook , "" . frontend- Facebook, . , , MVC ?
view.setState({})
view.forceUpdate()
. , kahi4.
Source: https://habr.com/ru/post/350850/
All Articles