Vueの双方向結合原理と用法の解明

11430 ワード

本論文の実例はVueの双方向結合原理と使い方を述べている。皆さんに参考にしてあげます。具体的には以下の通りです。
Vueで何を入力したいのかというと、自然に<input v-model="xxx" />を使って双方向結合を実現したいと思います。以下は一番簡単な例です。

<div id="app">
  <h2>What's your name:</h2>
  <input v-model="name" />
  <div>Hello {{ name }}</div>
</div>

new Vue({
  el: "#app",
  data: {
      name: ""
  }
});
この例の入力ボックスに入力された内容は、後で表示されます。これはVue原生による<input>への良好なサポートであり、親コンポーネントとサブアセンブリとの間の双方向データ転送の典型的な例でもある。しかし、v-modelはVue 2.2.0のみが加入する新しい機能であり、これまでは、Vueは一方向のデータストリームのみをサポートしていた。
Vueの一方通行のデータストリーム
Vueの一方向データストリームはReactと同様に、親コンポーネントは、サブアセンブリの属性(Props)を設定することによって、サブアセンブリにデータを転送することができ、親コンポーネントは、サブアセンブリのデータを取得したいので、サブアセンブリにイベントを登録し、サブアセンブリが嬉しいときに、このイベントをトリガしてデータを転送する。一言で言えば、Propsは下にデータを送り、イベントは上にデータを伝える。
上記の例では、v-modelを使用しないと、そうなるはずです。

<input :value="name" @input="name = $event.target.value" />
イベントの処理はインラインモードになっていますので、シナリオ部分は修正する必要がありません。しかし、多くの場合、イベントは一つの方法として定義されています。コードは複雑になります。

<input :value="name" @input="updateName" />

new Vue({
  // ....
  methods: {
    updateName(e) {
      this.name = e.target.value;
    }
  }
})
上記の例から見て、v-modelは多くのコードを節約しています。最も重要なのは一つのイベント処理関数を少なく定義できることです。したがって、v-model実際に行われたイベントは以下の通りです。
  • は、v-bind:)を使用して、一方向バインディングの属性(例::value="name"
  • を使用する。
  • バインディングinputイベント(@input)は、デフォルト実装のイベントハンドラ関数(例:@input=updateName
  • )に接続されている。
  • このデフォルトのイベントハンドラ関数は、イベントオブジェクトによって持ち込まれた値に基づいてバインディングされたデータを修正する(例:this.name = e.target.value

  • カスタムコンポーネントのv-model
    Vueはオリジナルのコンポーネントをカプセル化していますので、<input>は入力時にinputイベントをトリガします。しかし、カスタムコンポーネントはどうなりますか?ここではJs Fiddle VueサンプルのTodo Listの例を借りることができます。
    Js FiddleのVueサンプル
    Js FilddleのLogoをクリックして、上のポップアップパネルの中からVueサンプルを選択すればいいです。
    サンプルコードにはHTMLとVue(js)の二つの部分が含まれています。コードは以下の通りです。
    
    <div id="app">
     <h2>Todos:</h2>
     <ol>
      <li v-for="todo in todos">
       <label>
        <input type="checkbox"
         v-on:change="toggle(todo)"
         v-bind:checked="todo.done">
    
        <del v-if="todo.done">
         {{ todo.text }}
        </del>
        <span v-else>
         {{ todo.text }}
        </span>
       </label>
      </li>
     </ol>
    </div>
    
    new Vue({
     el: "#app",
     data: {
      todos: [
       { text: "Learn JavaScript", done: false },
       { text: "Learn Vue", done: false },
       { text: "Play around in JSFiddle", done: true },
       { text: "Build something awesome", done: true }
      ]
     },
     methods: {
       toggle: function(todo){
        todo.done = !todo.done
      }
     }
    })
    Todoコンポーネントを定義
    Js FiddleのVueテンプレートはデフォルトで一つのTodoリストの展示を実現します。データは固定されています。すべての内容は一つのテンプレートで完成します。まず私たちがやるべきことは一つのTodoを一つのサブアセンブリに変えることです。Js Fiddleでは多文書の形式が書けないので、コンポーネントはVue.component()を使ってシナリオの中で定義されています。主に<li>の内容の中の部分を持ち出します。
    
    Vue.component("todo", {
      template: `
    <label>
      <input type="checkbox" @change="toggle" :checked="isDone">
      <del v-if="isDone">
        {{ text }}
      </del>
      <span v-else>
        {{ text }}
      </span>
    </label>
    `,
      props: ["text", "done"],
      data() {
        return {
          isDone: this.done
        };
      },
      methods: {
        toggle() {
          this.isDone = !this.isDone;
        }
      }
    });
    アプリで定義されているtoggle()の方法も少し変更され、コンポーネント内に定義されました。toggle()が呼び出したときに、完了したかどうかを示すdoneの値が修正されます。しかし、donepropsに定義された属性であり、直接値を割り当てることができないので、公式推薦の第1の方法を採用して、データisDoneを定義してthis.doneに初期化し、コンポーネント内でisDoneを使用して、この状態が完了するかどうかを制御する。
    対応するアプリ部分のテンプレートとコードはかなり減量されました。
    
    <div id="app">
      <h2>Todos:</h2>
      <ol>
        <li v-for="todo in todos">
          <todo :text="todo.text" :done="todo.done"></todo>
        </li>
      </ol>
    </div>
    
    new Vue({
      el: "#app",
      data: {
        todos: [
          { text: "Learn JavaScript", done: false },
          { text: "Learn Vue", done: false },
          { text: "Play around in JSFiddle", done: true },
          { text: "Build something awesome", done: true }
        ]
      }
    });
    しかし、ここまでは、データはまだ一方的です。効果的には、チェックボックスをクリックすると線の削除効果がフィードバックされますが、これらの動的変化はすべてtodoコンポーネントの内部で行われ、データバインディングの問題がありません。
    Todo Listにカウントを追加します。todoコンポーネント内部の状態変化をTodo Listに表示させるために、Todo Listにカウントを追加し、完了したTodoの数を示します。この数はtodoコンポーネントの内部状態(データ)の影響を受けているので、todo内部データの変化をその親のコンポーネントに反映させる必要があり、これはv-modelの利用可能な場所である。
    この数量はタイトルの中でn/mの形で現れます。例えば、2/4の表現は全部で4つのTodoです。もう2つ完成しました。これはTodo Listのテンプレートとコード部分を修正し、countDonecountの2つの計算属性を追加する必要があります。
    
    <div id="app">
      <h2>Todos ({{ countDone }}/{{ count }}):</h2>
      <!-- ... -->
    </div>
    
    new Vue({
      // ...
      computed: {
        count() {
          return this.todos.length;
        },
        countDone() {
          return this.todos.filter(todo => todo.done).length;
        }
      }
    });
    現在はカウントが出ていますが、タスクの状態が変わってもこのカウントには影響がありません。私たちはサブコンポーネントの変動が親コンポーネントのデータに影響を与えます。v-modelは後でまた話します。一番よくある方法を使ってください。イベント:
  • サブコンポーネントtodotoggle()においてtoggleイベントをトリガし、isDoneをイベントパラメータ
  • とする。
  • 親コンポーネントtoggleイベント定義イベント処理関数
  • 
    Vue.component("todo", {
      //...
      methods: {
        toggle(e) {
          this.isDone = !this.isDone;
          this.$emit("toggle", this.isDone);
        }
      }
    });
    
    <!-- #app        -->
    <todo :text="todo.text" :done="todo.done" @toggle="todo.done = $event"></todo>
    ここで@toggleをバインドした表現です。ここでtodoは一時変数であるので、methodsにおいて特定のイベントハンドラ関数を定義すると、この一時変数を過去にバインドするのは難しい(もちろん、一般的な方法は呼び出しの形で実現できると定義される)。
    イベントハンドラ関数は、一般的にonToggle(e)を定義するなど、処理すべきことに直接に対応し、@toggle="onToggle"にバインディングされる。この場合、パラメータとしてtodoに入ることはできません。
    一般的な方法は、toggle(todo, e)として定義され、イベント定義において、関数として式を呼び出す形式で呼び出される。@toggle="toggle(todo, $event)"。 todo.done=$event`同属式。
    両者の違いに注意して、前者はバインディングの処理関数(参照)であり、後者はバインディングの表現(呼び出し)である。
    今はイベント方式によって、予想通りの効果がありました。
    v-modelに変換
    前に私達はv-modelで実現すると言いましたが、今改造してみます。v-modelのいくつかの要素を実現するように注意してください。
  • サブコンポーネントは、value属性(Prop)を介して入力を受け付ける
  • サブコンポーネントは、inputイベントをトリガすることによって出力され、配列パラメータ
  • 親コンポーネントの中でv-model
  • をバインドする。
    
    Vue.component("todo", {
      // ...
      props: ["text", "value"],  // <--    done     value
      data() {
        return {
          isDone: this.value  // <--    this.done     this.value
        };
      },
      methods: {
        toggle(e) {
          this.isDone = !this.isDone;
          this.$emit("input", this.isDone); // <--         
        }
      }
    });
    
    <!-- #app        -->
    <todo :text="todo.text" v-model="todo.done"></todo>
    .syncは他のデーターバインディングを実現します。
    前に述べたように、Vue 2.2.0はv-modelの特性を導入します。いくつかの理由で、入力属性はvalueですが、出力イベントはinputです。v-modelvalueinputの3つの名称は字面の上からいささかの関係が見えません。これはちょっと変わったように見えますが、これはポイントではなくて、コントロールは一つの属性を双方向に結合するしかないですか?
    Vue 2.3.0は.syncの修飾語を導入して、双方向結合にするv-bindを修飾する。これは同様にシンタックス飴であり、:によって修正されたデータバインディングは、.syncのように、バインディングされたデータを自動的に登録することができる。この方法は、サブアセンブリが特定のイベントをトリガするのと同じである。しかし、このイベントの名前は、どうやらバインディング属性名と少し関係があり、バインディング属性名の前にv-modelプレフィックスを追加する。
    例えば、update:は、サブアセンブリの<sub :some.sync="any" />属性を親コンポーネントのsomeデータと結合し、サブコンポーネントの中でanyを介して変更をトリガする必要がある。
    上記の例では、$emit("update:some", value)を使用したバインディングは常に違和感を感じています。v-modelの字面の意味は、双方向結合された数値であり、未完成のv-modelは、実際には値ではなく状態であることを表しています。したがって、私たちは再びそれを修正し、依然としてdoneという属性名を使用して、doneを通じて双方向結合を実現しています。
    
    Vue.component("todo", {
      // ...
      props: ["text", "done"],  // <--     done
      data() {
        return {
          isDone: this.done  // <--     done
        };
      },
      methods: {
        toggle(e) {
          this.isDone = !this.isDone;
          this.$emit("update:done", this.isDone); // <--     :update:done
        }
      }
    });
    
    <!-- #app        -->
    <!--    v-model     :done.sync,       -->
    <todo :text="todo.text" :done.sync="todo.done"></todo>
    公開Vue双方向バインディング
    上記の説明により、Vueの双方向バインディングは、実際には通常の一方向バインディングとイベントの組み合わせで行われていることがわかったと思いますが、value.syncでデフォルトの処理関数を登録してデータを更新しました。Vueソースの中にはこのような段があります。
    
    // @file: src/compiler/parser/index.js
    
    if (modifiers.sync) {
      addHandler(
        el,
        `update:${camelize(name)}`,
        genAssignmentCode(value, `$event`)
      )
    }
    このコードからは、v-model双方向バインディングの際に、コンパイラは.syncのイベントハンドラを追加してデータを割り当てていることが分かります。
    展望
    現在Vueの双方向バインディングは、イベントをトリガすることによってデータフィードバックを実現する必要があります。これは多くの期待されているものとはまだ差があります。この差を作った主な原因は二つあります。
  • は、イベントを通じてデータをフィードバックする必要がある
  • 属性は
  • に値しません。
    現在のVueバージョンでは、計算属性を定義することによって簡略化することができます。
    
    computed: {
      isDone: {
        get() {
          return this.done;
        },
        set(value) {
          this.$emit("update:done", value);
        }
      }
    }
    実を言うと、同じ名前の変数名を多く定義するのも大変です。Vueは、将来のバージョンにおいて、属性宣言のために.syncオプションを追加するなど、一定の技術的手段によって、このプロセスを減少させることができることが望ましい。
    もちろんフレームとしては、問題を解決する時に、他の特性への影響やフレームの拡張性などを考慮しなければならないので、最終的には双方向バインディングはどのように進展しますか?
    興味のある友達はオンラインHTML/CSS/JavaScriptコードを使ってツールを実行できます。http://tools.jb51.net/code/HtmlJsRun上記コードの運行効果をテストします。
    ここで述べたように、皆さんのvue.jsプログラムの設計に役に立ちます。