Vue.js で子コンポーネントから親に値を送る(SVG編)

この手の記事は結構あるのだけど、毎度忘れてしまうので備忘録的に。

やりたいこと

こんな風にSVGにピンを置きます。Vue.js を使って、SVG画像のピンの位置を親の App.vue に伝えます。

App.vue

親コンポーネント(App.vue)のデータで持つのは map_x と map_y で、data() の戻り値で保持します。これは、そのまま App.vue 内で {{map_x}} で参照が可能です。

方眼紙とピンで表示は Hello.vue で指定できるようにします。
Hello.vue 側は max_x と max_y プロパティ、変更したときのイベントを updatePos で返せるようにしてあります。

<template>
  <div id="app">
    <div>map_x: {{map_x}}</div>
    <div>map_y: {{map_y}}</div>
    <Hello 
     :map_x="map_x" 
     :map_y="map_y" 
     @updatePos="updatePos" />
  </div>
</template>

<script>
import Hello from './components/Hello.vue'

export default {
  name: 'App',
  components: {
    Hello,
  },
  data() {
    return {
      map_x: 100,
      map_y: 100,
    };
  },
  methods: {
    updatePos(x,y) {
      this.map_x = x ;
      this.map_y = y ;
    }
  }
}
</script>

Hello.vue

親の App.vue から max_x と map_y を受け取るのがプロパティで props に記述します。ピンはマウスのクリック(v-on:mousedown)で動かせるようにしてあるので、別途データとして pin_x と pin_y を用意します。
プロパティの max_x と max_y は親から引き渡される読み取り専用のパラメータなので別途代入しています。

実際は、pin_x と pin_y は、ピンの先端に座標が合うように少し補正してあります。

<template>
  <div>
    <h1>Hello SVG</h1>
    <div>
      <svg
           ref="pic"
           viewBox="0 0 600 400"
           v-on:mousedown="mousedown()"
      >
            <image 
              href="@/assets/images/hogan.jpg" />
            <image
               :x="pin_x"
               :y="pin_y"
               width="20px"
               height="58px"
               href="@/assets/images/marker.svg"
            />
      </svg>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Hello',
  props: {
    map_x: Number,
    map_y: Number,
  },
  data() {
    return {
      pin_x: this.map_x - 10 ,
      pin_y: this.map_y - 40 ,
    }
  },
  methods: {
    mousedown() {
        const vbox = this.$refs.pic.viewBox ;
        const pic_w = vbox.baseVal.width ;
        const pic_h = vbox.baseVal.height ;
        var r = this.$refs.pic.getBoundingClientRect();
        var x = Math.round(Math.round(event.clientX-r.left) * pic_w/r.width) ;
        var y = Math.round(Math.round(event.clientY-r.top)  * pic_h/r.height);
        this.pin_x = x - 10 ;
        this.pin_y = y - 40 ;
        this.$emit('updatePos', x, y );
    },
  },
}
</script>

マウスをクリックしたときの mousedown 関数では、クリックした座標を補正するためにちょっとややこしいことになっています。

クリックしたときの座標を返すために、this.$emit で updatePos イベントを呼び出します。

Vue 親子コンポーネントの関係は MVVM パターンではない

つまりは、

  • 親から子コンポーネントに渡すときは max_x, max_y のようにプロパティ経由で渡す
  • 子から親コンポーネントに渡すときは updatePos イベントのように this.$emit を通して渡す

のようになるので、MVVM パターンのように対称ではありません。Vue.js が MVVM パターンなのは、ひとつのコンポーネント内での仮想 DOM とのやり取りの話であって、親子コンポーネントは oneway のプロパティ経由なのだ、と。

という訳で、勘違いして迷っていたのでメモ書き。

カテゴリー: 開発 パーマリンク