🎉

なぜVueを始めるべきか:アーキテクチャーについて

2024/08/20に公開

概要

今まではReactを採用されているプロダクトに参画したことが多かったですが、
今後はVueを採用しているプロジェクトに参画することが決まり
勉強した内容を整理・共有したいと思っています。

今回はこの一つのステップでまずは
両方のアーキテクチャーについて話しようかと思っています。

アーキテクチャー:VueとMVVMパータン

まずは概要で少し話した通りVueの理解を深めるために
この二つのアーキテクチャーについて比較する必要があると思っています。

Vueの場合、ライブラリーではなくJavaScriptフレームワークと言うものなので
何のアーキテクチャーを使っているかがしっかり記載されています。

MVVM(Model-View-ViewModel)と言うソフトウェアアーキテクチャーを採用しています。

いわゆる、MVC(Model-View-controller)の派生であります。


上の画像はMCVパータンのフロー表示しています。

相違点としてはcontrollerの役割をViewModelと言うところで担当しています。
これは色んな意味を含めていますが、
まずはcontrollerがViewに含めているアーキテクチャーのため、
実際ユーザーはControllerではなくViewとコミュニケーションができる点です。
それによってユーザーの立場ではMVCパータンよりは画面の切り替えが早く感じことも可能でしょう。

次はデータの反映方法相違点の一つです。
MVCパータンの場合、Modelからデータを読み込んでControllerからUIアップデートの命令を受けていますが、
MVVCパータンの場合、全部View(View, ViewModel)からDataBindingと言う名前でコミュニケーションを取っています。

個人的にはMVVCパータンのViewModelと言う存在でReactとVueの特徴である仮想DOMが生まれたことではないかと思います。

もちろんこの二つを採用しているフレームワークによってデータ反映方法とかの相違がある時がありますが、基本的なノードは同じなので概念だけ理解しているであれば特に問題はないかと思います。


上の画像はVueの公式ドキュメントで公開されているMVVCの画像になります。
公式ページを参考してください。
https://012.vuejs.org/guide/

アーキテクチャー:ReactとFluxパータン

VueとAngularがフレームワークと呼ばれますけど
実はReactはライブラリーと言うものなので公式ドキュメント内では
Reactがどんなアーキテクチャーを採用されているか記載されてないです。

多分React内部ではVueと同じく
MVVMパータンを採用している可能性が高いと思いますが、

過去FacebookチームでFluxと言うパータンを公開した時に
過去ReactはMVCパータンを利用したが、
何かの問題のためFluxと言うものを作って採用していると説明しているので
今回はこのFluxと言うものについて話しようかと思っています。

もちろん、Fluxパータンは現状、Redux,Recoil,zustandなどの
外部ライブラリー拡張されFluxパータンはもう使われないですが、
Reactを理解するためには十分かと思います。

ReactとMVCパータン

上で少し話した通り最初Reactでは一般的なMVC(Model-View-Controller)採用したと言っています。

当時のFaceBookでは
読んでないメッセージの数を表示する同時に別のViewで
読んでない一つのハイライトを表示するようにしたかったが
これをシングルスレッドで実装するのが難しいだったと話しています。

私はFacebookを良く使ってないので
この件についてネットで少し調査して見ましたが、
通知アイコンには何個かの読んでないメッセージがあると表示されていて
入って見ると実際には新規メッセージがないと言うバグがあったらしいです。

特にFacebook見たいに大きなアプリケーションだと十分ありそうですね。


上の画像はFacebookチームのプレゼンで使っている画像になります。
実際Facebookと言うアプリ内でどのように動いているかは分からないですが、
色んなViewModelにデータ参照・データ更新などのリクエストをすることは確実ですね。

MVCパータンを採用している大きなアプリの場合、データ間の依存性問題が発生してしまい
データの流れを予測できなくなる問題があったらしいです。[1]

We originally set out to deal correctly with derived data: for example, we wanted to show an unread count for message threads while another view showed a list of threads, with the unread ones highlighted. This was difficult to handle with MVC — marking a single thread as read would update the thread model, and then also need to update the unread count model. These dependencies and cascading updates often occur in a large MVC application, leading to a tangled weave of data flow and unpredictable results.

Fluxドキュメント(アーカイブ) In-Depth Overview

その以後、この問題を解決するため新しいプロジェクトが始まり
単方向データバインディング(One-way Data Binding)を採用しているFluxと言うパータンが作られました。

ReactとFluxパータン


上の画像はシンプルなFluxパータンのデータフローを表示しています。


上の画像は実施Viewからイベントが発生した時の全体的なフローを表示しています。

まずは各ノードの役割とコードを簡単に確認しましょう。

  • Action
    主にViewからトリガーされるノード
var TodoActions = {
  /**
   * @param  {string} text
   */
  create: function(text) {
    AppDispatcher.handleViewAction({
      actionType: TodoConstants.TODO_CREATE,
      text: text
    });
  },
 
};

上のcreate関数が実行されたら以下のデータが格納されます。

{
  source: 'VIEW_ACTION',
  action: {
    type: 'TODO_CREATE',
    text: '入力したデータ'
  }
}
  • Dispatcher
    Fluxアーキテクチャーで全てのデータフローを管理する'中央ハブ'
    Storeが登録されていて
    もしViewからActionが実行されたら
    Actionからタイプ(type)とデータ(payload)を受けて該当するCallbackをStoreに伝送します。
var AppDispatcher = assign({}, Dispatcher.prototype, {
  ・・・
  handleViewAction: function(action) {
    this.dispatch({
      source: 'VIEW_ACTION',
      action: action
    });
  }
});
  • Store
    実際のデータ(状態)とビジネスロジックが含めているノード
    Dispatcherから伝送されたCallbackを利用して該当するロジックを実行(状態更新)します。
    Storeにより状態が更新されたらViewに状態を転送します。
function create(text) {
  var id = Date.now();
  _todos[id] = {
    id: id,
    complete: false,
    text: text
  };
}
・・・
var TodoStore = assign({}, EventEmitter.prototype, {
 ・・・
  dispatcherIndex: AppDispatcher.register(function(payload) {
    var action = payload.action;
    var text;

    switch(action.actionType) {
      case TodoConstants.TODO_CREATE:
        text = action.text.trim();
        if (text !== '') {
          create(text);
         ・・・
        }
        break;
      ・・・
    }
    ・・・
    
  })
・・・
});
  • View
    React内には'View'以外にもView仲裁者役割をする
    最上位の'View-Controller'と言うViewが存在するが、
    View-ControllerはStoreから更新された状態を子供のViewにまた転送します。
var TodoActions = require('../actions/TodoActions');

var Header = React.createClass({

  /**
   * @return {object}
   */
  render: function() {
    return (
      <header id="header">
        <h1>todos</h1>
        <TodoTextInput
          id="new-todo"
          placeholder="What needs to be done?"
          onSave={this._onSave}
        />
      </header>
    );
  },

  /**
   * @param {string} text
   */
  _onSave: function(text) {
    TodoActions.create(text);
  }

});
var TodoItem = React.createClass({
 ・・・
  render: function() {
    var todo = this.props.todo;

    return (
      <li
        key={todo.id}>
        <label>
          {todo.text}
        </label>
        <button className="destroy" onClick={this._onDestroyClick} />
      </li>
    );
  },
  _onDestroyClick: function() {
    TodoActions.destroy(this.props.todo.id);
  }
});

データフロー予測

次は上の例コードで実際どのように各ノードが動くかを予測してみましょう。

Todoのアプリケーションがあり、
テキストボックスに入力したデータと入力し登録ボタンを押下した前提になります。

①~③について

まずは①から②はViewからトリガーされActionが実行され、
Dispatcherに値を転送します。
Storeに命令をするための、オブジェクトを作成するプロセスになります。

TodoActionオブジェクト内のcreateと言うアクションは
テキストボックスに入力した値が格納されているtext
システム内部で定義されているactionType
handleViewActionと言うDispatcherに渡します。

その後、DispatcherはCallbackで作成したデータをStoreに転送します

④~⑤について

Callbackで渡されたStoreAction内のTypeを確認して
ビジネスロジック実行、
その後状態が変更されたのを検知したReactは再レンダリングを行い
新規作成されたTodoを画面に表示することになります。

VueアーキテクチャーとReactアーキテクチャーの違い

VueとReactの大きな違い、つまり大きな枠であるアーキテクチャーの違いは
View間のデータ交換を単方向データバインディング(One-way Data Binding)でするか双方向データバインディング(Two-way Data Binding)でするかで理解できます。

次はの記事ではこのバインディングについて詳細に話したいと思いますが、
今回は簡単に概念だけ話して見ましょう。

単方向データバインディング(One-way Data Binding)

結論的にはViewとModel間にどのやってModel中にあるデータを登録・更新して
このデータをViewに反映するかと言う問題に対するソリューションになります。


単方向データバインディングの場合は
ViewはModelからデータが自動で受けますが、
逆にView内のデータをModelまでに更新する必要がある場合は
イベントトリガーで明示的にModelに伝える必要があります。

つまり別のコードを作成する必要があります。

Reactと言うJavScriptライブラリーがこの単方向データバインディングを採用しています。

双方向データバインディング(Two-way Data Binding)


双方向データバインディングの場合は
Viewで更新されたデータも自動でModel内のデータも更新します。

つまり別のコード作成する必要がなく、
このバインディング方法を採用しているフレームワークがあれば
バインディングについてはフレームワークに任せる感じになるでしょう。

VueとAngularと言うJavaScriptフレームワークがこの双方向データバインディングを採用しています。

なぜVueを使うべきか

アーキテクチャーだけで見ると結論的には以下の理由で双方向データバインディングほうがメリットがあると考えられます。

  • 双方向データバインディングを採用しており、
    ViewとModel間のデータ更新におけるコード作成不要
  • Vueの場合単方向データバインディングも選択可能
  • Vueはフレームワークのため、開発環境構築の時間を減少可能

特に初心者向けまで考えられるとReactよりはデータバインディングためのコードを作成する必要がないVueを採用するほうが良いでしょう。

ただ、次の記事で詳しく話すつもりではありますが、
双方向データバインディングを採用することにより以下のデメリットがあります。

  • データフローが複雑になり、データのデバッグが難しい

上でFaceBookチームが直面した問題になりますね。
単方向データバインディングだとデータフローは決まっているので
開始から終了までゆっくり確認すれば良いのため、デバッグが割としやすいでしょう。

もちろん問題解決には正解がないので、
両方メリット・デメリットをしっかり理解しているであれば
どっちでも問題ないかと思います。

背中には完璧なアーキテクチャーはないですからね。

次の記事ではこの両方のバインディングについて詳細に話したいと思っています。

参照

https://012.vuejs.org/guide/
https://en.wikipedia.org/wiki/Model–view–viewmodel

脚注
  1. https://facebookarchive.github.io/flux/docs/in-depth-overview ↩︎

GitHubで編集を提案

Discussion