Vue.jsで親子関係のコンポーネントを作る

【前提条件】

[環境]
  • Vue.js 0.12.12
[参考サイト]

Vue.jsのAPIリファレンス

【概要】

今回はVue.jsで子要素を追加する方法です。

と言っても、コンポーネントに登録せずにやる方法とコンポーネントに登録する方法があります。

コンポーネントに登録しないやり方は正しくないように思えるので微妙です。
コンポーネントに登録しない方法はとりあえず動くというものです。
とりあえず動くはよろしくないですね。

現在、1.0.0alphaが公開されているので今後は変わる可能性があります。

【コンポネーネントに登録しない方法】

[JavaScript]
var Child = Vue.extend({
  data: function() {
    return {
      name: ""
    };
  },
  methods: {
    sayHello: function() {
      this.$dispatch("hello", this.name);
    }
  }
});

var Parent = Vue.extend({
  ready: function() {
    this.leftChild = this.$addChild({ el: "div.left-child", data: { name: "レフト" } }, Child);
    this.rightChild = this.$addChild({ el: "div.right-child", data: { name: "ライト" } }, Child);
  },
  data: function() {
    return {
      parentMessage: "",
      childMessage: ""
    };
  },
  events: {
    hello: function(name) {
      this.parentMessage = "Hi child!";
      this.childMessage = "Hi I am " + name;
    }
  }
});

var helloComponent = new Parent({ el: "div.parent-component" });

ready関数内で$addChildにより対象のDOMを子として追加しています。
子の方は$dispatchで親にイベント通知を行っています。

[html]
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8"/>
    <title>Vue.js Child DOM</title>
    <style>
      div.children {
        display: flex;
      }
      div.children > div {
        border: solid 1px #999999;
        padding: 10px;
        margin-left: 5px;
      }
    </style>
  </head>
  <body>
    <div class="parent-component">
      <dl class="talk">
        <dt>Parent</dt>
        <dd v-text="parentMessage"></dd>
        <dt>Child</dt>
        <dd v-text="childMessage"></dd>
      </dl>
      <div class="children">
        <div class="left-child" v-pre>
          <dl>
            <dt>Name</dt>
            <dd v-text="name"></dd>
          </dl>
          <button v-on="click: sayHello">Hello</button>
        </div>
        <div class="right-child" v-pre>
          <dl>
            <dt>Name</dt>
            <dd v-text="name"></dd>
          </dl>
          <button v-on="click: sayHello">Hello</button>
          </div>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/0.12.12/vue.js"></script>
    <script src="sample.js"></script>
    </body>
</html>

div.parent-componentが親となるDOMです。

div.childrenの下にあるdiv.left-childとdiv.right-childが子となるDOMです。
子となるDOMにはv-preをつけます。
これをつけないと動かないです。

v-preはコンパイルのスピードを上げるためのものなので、本来は使い方とは別です。
ただ、これをつけないと親要素のがDOMをロックしてしまうためなのか動かないです。

コンポーネントに追加する方法】

[2015/09/17 追記]コンポーネントのやり方は勘違いしていました。
このコードは動きますが理解が正しくないようです。
後日、新しいエントリ書きます。たぶん・・・

コンポーネントについて新しいエントリかきました。
Vue.componentとcomponentオプションの違い - シュンツのつまづき日記

[JavaScript]
var ChildComp = Vue.extend({
  props: ["userName"],
  template: "#child-template",
  methods: {
    sayHello: function() {
      this.$dispatch("hello", this.userName);
    }
  }
});
Vue.component("child", ChildComp);

var Parent = Vue.extend({
  data: function() {
    return {
      parentMessage: "",
      childMessage: ""
    };
  },
  components: {
    left: ChildComp,
    right: ChildComp
  },
  events: {
    hello: function(name) {
      this.parentMessage = "Hi child!";
      this.childMessage = "Hi I am " + name;
    }
  }
});

var helloComponent = new Parent({ el: "div.parent-component" });

先ほどとは違い子コンポーネントの方はpropsとtemplateが出現しました。
propsの方はこのコンポーネントに渡せるプロパティ名を文字列で指定します。

templateはレンダリングするDOMを書きます。
DOMの文字列を直接書いても良いのですが、今回は同一ページ内のID指定で書いています。

作成した子コンポーネントはVue.componentでコンポーネントに登録します。
最初の引数のパラメータはHTML上で参照する時のタグ名として使用します。


続いて、親コンポーネントです。
コンポーネントはcomponentsに子コンポーネントを登録します。
今回はleftとrightという名前で登録しています。

[HTML]
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8"/>
    <title>Vue.js Child DOM</title>
    <style>
      div.children {
        display: flex;
      }
      div.children > div {
        border: solid 1px #999999;
        padding: 10px;
          margin-left: 5px;
      }
    </style>
  </head>
  <body>
    <div class="parent-component">
      <dl class="talk">
        <dt>Parent</dt>
        <dd v-text="parentMessage"></dd>
        <dt>Child</dt>
        <dd v-text="childMessage"></dd>
      </dl>
      <div class="children">
        <child user-name="レフト" v-ref="left"></child>
        <child user-name="ライト" v-ref="right"></child>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/0.12.12/vue.js"></script>
    <script type="text/x-template" id="child-template">
      <div>
        <dl>
          <dt>Name</dt>
          <dd v-text="userName"></dd>
        </dl>
        <button v-on="click: sayHello">Hello</button>
      </div>
    </script>
    <script src="sample.js"></script>
  </body>
</html>
                                                 

先ほどVue.componentで登録した時の名前でタグを指定します。
今回のサンプルではになります。
propsに指定した名前の属性使用することができます。

componentsで指定した名前をv-refに指定することで
DOMとコンポーネントのオブジェクトを紐づけることができます。

最後のtext/x-template形式は子コンポーネントのDOMになります。
コンポーネントのtemplateで設定した値をscriptタグのid属性に設定します。

【動きを見たい】

addChild
components
こちらに動くコードを置いてあります。

【まとめ】

コンポーネントを登録するやり方は2つあります。
が、Vue.componentでやる方が正しいなあと思います。

サーバサイドでHTMLを生成してそこにVue.jsを使うという場合は
Vue.componentではない方法でやれると嬉しいかなと思ったりもします。
HTMLはHTMLで独立させたいのでできればcomponentsに登録しない、
v-preも使わない方法で動いて欲しいなと思ったり。