Reduxが本当に必要ですか?

少し前まで、Reactは自身を「MVCのV」として位置付けていました。 このコミットマーケティングテキストは変更されましたが、本質は同じままです。Reactが表示を担当し、開発者が他のすべてを担当します。つまり、MVCの観点から言えば、ModelとControllerを担当します。


アプリケーションのモデル(状態)を管理するためのソリューションの1つはReduxです。 その外観は、MVCが処理できないフロントエンドアプリケーションの複雑さの増加に動機付けられています。


チーフテクニカルインペラティブソフトウェア開発-複雑性管理

- 完全なコード

Reduxは、予測可能な状態変化で複雑さを管理することを提案しています。 予測可能性は、 3つの基本原則によって達成されます。



Reduxは増加する複雑さを克服でき、戦うべきものはありましたか?


MVCはスケーリングしません


Reduxは、FacebookソリューションであるFluxに触発されています。 Facebook開発者ビデオ )によると、Fluxを作成した理由は、MVCアーキテクチャパターンのスケーラビリティの問題でした。


Facebookによると、MVCを使用した大規模プロジェクトでのオブジェクトの関係は予測不能になります。


  1. modelOneはviewOneを変更します
  2. viewOneは変更中にmodelTwoを変更します
  3. modelTwoは変更中にmodelThreeを変更します
  4. modelThreeは、変更中にviewTwoとviewFourを変更します

MVCの変更の予測不可能性の問題は、Reduxの動機にも書かれています。 以下の図は、Facebook開発者がこの問題をどのように見ているかを示しています。



Fluxは、説明されているMVCとは対照的に、理解可能で細身のモデルを提供します。


  1. スポーンアクションを表示
  2. アクションがDispatcherに入る
  3. ディスパッチャー更新ストア
  4. 更新されたストアは変更のビューを通知します
  5. 再描画を表示


さらに、Fluxを使用して、いくつかのビューは関心のあるストアをサブスクライブし、これらのストアで何かが変更されたときにのみ更新できます。 このアプローチにより、依存関係の数が減り、開発が簡素化されます。



FacebookのMVC実装は、Smalltalkの世界で広く配布されていた元のMVCとはまったく異なります。 この違いが、「MVCはスケーリングしない」というステートメントの主な理由です。


80年代に戻る


MVCは、Smalltalk-80でユーザーインターフェイスを開発するための主なアプローチです。 FluxやReduxと同様に、MVCはソフトウェアの複雑さを軽減し、開発を迅速化するために作成されました。 MVCアプローチの基本原理について簡単に説明します 。より詳細な概要については、 こちらこちらをご覧ください


MVCエンティティの責任:



そして今、FacebookがMVCを実装することで見逃していたもの-これらのエンティティ間の関係:



下の画像をご覧ください。 モデルからコントローラーおよびビューに向けられた矢印は、その状態を変更しようとするものではなく、モデルの変更に関する通知です。



元のMVCは、Viewが多くのモデルを変更でき、Modelが多くのViewを変更でき、ControllerはViewと密接な1対1の関係を形成しないというFacebookの実装とはまったく異なります。 さらに、FluxはMVCであり、DispatcherとStoreがモデルの役割を果たし、メソッドを呼び出す代わりにアクションが送信されます。


MVCのプリズムを介して反応する


単純なReactコンポーネントのコードを見てみましょう。


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 .


MVC


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 , , Redux . frontend- :



Redux , .


Redux , , , . Redux indirection , Presentation Components , Action' State, props. indirection' . , .


indirection' TodoMVC, Redux. State callback' onSave, ?


,
  1. hadleSave TodoItem props onSave TodoTextInput
  2. onSave Enter , props newTodo, onBlur
  3. hadleSave props deleteTodo, , props editTodo
  4. props' deleteTodo editTodo TodoItem MainSection
  5. MainSection props' deleteTodo editTodo TodoItem
  6. props' MainSection App bindActionCreator, action' src/actions/index.js, src/reducers/todos.js

, callback', props', 2 . , .


MVC, , . indirection' , .


Flux Redux MVC, , MVC. Redux , callback' props' , . frontend-, Flux Redux, . . Facebook , "" . frontend- Facebook, . , , MVC ?


UPD


view.setState({}) view.forceUpdate(). , kahi4.



Source: https://habr.com/ru/post/J350850/


All Articles