作りたいものがありすぎる

40歳を過ぎてプログラミングを始めた人の顛末とこれからなど

【輪読会資料】基礎から学ぶVue.js CHAPTER5 コンポーネントでUI部品を作る 読書メモ

今回は輪読担当ではなかったので、完全な自分のメモですが、アップしておきます。

var で定義できるローカルのコンポーネントもある、

// コンポーネントを定義
var myComponent = {
  template: '<p>MyComponets</p>'
}
new Vue({
  el: '#app',
  componets: {
    // 処理
  'myComponet' : myComponet
  }
})

コンポーネント間の通信
P155

親から子

// コンポーネントの定義 comp-child
Vue.component('comp-child', {
  // テンプレートで受け取ったvalを使用
  template: '<p>{{ val }}</p>',
  // 受け取る属性名を指定
  props: ['val']
})

// 親コンポーネント
new Vue({
  el: '#app',
  data: {
    valueA: 'これは子A',
    valueB: 'これは子B'
  }
})
<div id="app">
    <comp-child val="これは子A"></comp-child>
    <comp-child val="これは子B"></comp-child>
</div>

出力結果 子コンポーネント

で囲む状態が出力されている

<p>これは子A</p>
<p>これは子B</p>

propsで受け取ってないものは子供側では上書きが基本、class等の属性は親と子でマージされ両方反映

コンポーネントのルートタグ

Vue.component('comp-child', {
    template: '<p id="child" class="child">ChildComponent</p>',
})

コンポーネントのカスタムタグ

<comp-child id="parent" class="parent"></comp-child>

出力される実態

<comp-child id="parent" class="child parent">ChildComponent</comp-child>


// 子
Vue.component('comp-child', {
    template: '<li>{{ name }} HP.{{ hp }}</li>',
    props: ['name', 'hp']
})
// 親
new Vue({
    el: '#app',
    data: {
      list: [
        { id: 1, name: 'スライム', hp: 100 },
        { id: 2, name: 'ゴブリン', hp: 200 },
        { id: 3, name: 'ドラゴン', hp: 500 }
      ]
    }
})

list は親を使っている name, hp は 子のprops定義で使える様になっている

<!-- これも親となる  comp-child で子を呼び出し-->
<ul>
    <comp-child v-for="item in list"
        v-bind:key="item.id"
        v-bind:name="item.name"
        v-bind:hp="item.hp"></comp-child>
</ul>

コンポーネントで値を書き換えてもconsoleに[Vue warn]が出力される。
当然親の値は書き換えられない

Vue.component('comp-child', {
  template: '<li>{{ name }} HP.{{ hp }}\
  <button v-on:click="doAttack">攻撃する</button></li>',
  props: ['name', 'hp'],
  methods: {
    doAttack: function () {
      // 勝手に攻撃!
      this.hp -= 10 // -> [Vue warn] error!
    }
  }
})

もし、値の書き換えを行いたいなら算出プロパティを使って新しいデータを作成する。
もし親のデータ自体を変更したい場合は、$emitを使って親にアクションを起こさせる。等する

propsで値を受け取る際は、データ型を指定しておくのが推奨されている。

Vue.component('comp-child', {
    props: {
        val: String  // 文字列型のみ許可
    }
])

型の指定方法一覧

データ型 説明
String 文字列 '1'
Number 数値 1
Boolean 真偽値 true, false
Function 関数 function() {}
Object オブジェクト { name: 'foo' }
Array 配列 [1, 2, 3], [{ id: 1 }, { id: 2 }]
カスタム インスタンス new Cat()
null すべての型 1, '1', [1]

型チェック無しの場合の書き方

Vue.component('example', {
  props: ['value'] // どんな型も受け入れる
})

型チェックする場合の書き方

Vue.component('example', {
  props: {
    value: // ここにデータ型を指定
  }
})

型チェック以外にオプションで様々なバリデーションに対応できる
参照元
ほとんどControllerみたいになってきた。

Vue.component('my-component', {
  props: {
    // 基本的な型の検査 (`null` と `undefined` は全てのバリデーションにパスします)
    propA: Number,
    // 複数の型の許容
    propB: [String, Number],
    // 文字列型を必須で要求する
    propC: {
      type: String,
      required: true
    },
    // デフォルト値つきの数値型
    propD: {
      type: Number,
      default: 100
    },
    // デフォルト値つきのオブジェクト型
    propE: {
      type: Object,
      // オブジェクトもしくは配列のデフォルト値は
      // 必ずそれを生み出すための関数を返す必要があります。
      default: function () {
        return { message: 'hello' }
      }
    },
    // カスタマイズしたバリデーション関数
    propF: {
      validator: function (value) {
        // プロパティの値は、必ずいずれかの文字列でなければならない
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})

子から親

P161
字のデータを親に渡すには$emitを使う

親のコード
on で受け取る

<child-comp v-on:childs-event="parentMethod">

子のコード
$emitで渡す

this.$emit('childs-event')

parentsMethodchilds-eventを通じて親に渡る

<comp-child v-on:childs-event="parentsMethod"></comp-child>
Vue.component('comp-child', {
    template: '<button v-on:click="handleClick">イベント発火</button>',
    methods: {
      // ボタンのクリックイベントのハンドラでchilds-eventを発火する
      handleClick: function () {
        //  `parentsMethod`が`childs-event`を通じて親に渡る
        this.$emit('childs-event')
      }
    }
})
new Vue({
    el: '#app',
    methods: {
      // childs-eventが発生した!
      parentsMethod: function () {
        alert('イベントをキャッチ! ')
      }
    }
})

親が持つデータを操作する

前述のモンスターのHPを子のメソッドで処理していた例を動くものにしたのが以下の例
良く読み込んで動きはなんとか理解はしたけど、今は書ける気がしない...。

<ul>
    <comp-child v-for="item in list"
        v-bind:key="item.id"
        v-bind:name="item"
        v-on:attack="handleAttack"></comp-child>
</ul>
Vue.component('comp-child', {
    template: '<li>{{ name }} HP.{{ hp }}\
    <button v-on:click="doAttack">攻撃する</button></li>',
    props: {
      id: Number,
      name: String,
      hp: Number
    },
    methods: {
      // ボタンのクリックイベントのハンドラから$emitでattackを発火する
      doAttack: function () {
        // 引数として自分のIDを渡す v-on:attackの中身`handleAttack`を親に渡す
        this.$emit('attack', this.id)
      }
    }
  })

new Vue({
    el: '#app',
    data: {
      list: [
        { id: 1, name: 'スライム', hp: 100 },
        { id: 2, name: 'ゴブリン', hp: 200 },
        { id: 3, name: 'ドラゴン', hp: 500 }
      ]
    },
    methods: {
      // attackが発生した!
      handleAttack: function (id) {
        // 引数のIDから要素を検索
        var item = this.list.find(function (el) {
          return el.id === id
        })
        // HPが0より多ければ10減らす
        if (item !== undefined && item.hp > 0) item.hp -= 10
      }
    }
})

カスタムタグのイベントハンドリンク

この書き方だと、コンポーネント側からclick$emitで呼び出さないと発火しないらしい。

<my-icon v-on:click="handleClick"></my-icon>

元々のイベントは直接発火したい場合は.native修飾子を付ける。

<my-icon v-on:click.native="handleClick"></my-icon>

非親子コンポーネント

親子関係以外だと、this,propsの通信はできない。
Vueインスタンスを「イベントバス」として使用するが、多用しない方が良い。コードがカオス化するので、詳細はP166参照

コンポーネントを参照する「$refs」

親コンポ―ネント

<comp-child ref="child">

親のメソッド内

new Vue({
  el: '#app',
  methods: {
    handleClick: function () {
      // 子コンポーネントのイベントを発火
      this.$refs.child.$emit('open')
    }
  }
})

コンポーネント

Vue.component('comp-child', {
  template: '<div>...</div>',
  created: function () {
    // 自分自身のイベント
    this.$on('open', function () {
      console.log('なにか処理')
    })
  }
})

親側からはv-onを使うようにする

コンポーネントの属性のスコープ

コンポーネントの属性の値部分は親のスコープになる

<comp-child v-on:child-event="親のメソッド">
<comp-child v-on:child-event="parentMethod(親のデータ)">

コンポーネントが引数をもって$emitを実行している場合、子コンポーネントの引数は$event変数で使用できる

<comp-child v-on:child-event="parentMethod($event, parantsData)">
new Vue({
    el: '#app',
    data: {
        parentsData: '親のデータ'
    },
    methods: {
        methods: {
            parentsMethod: function(childArg, parentsArg) {
                //
            }
        } 
    }
})

$event変数は$emitの第一引数しか持たないので、複数の引数を渡す際は1つのオブジェクトにまとめる。

this.$emit('childs-event', {id: 1, name: '新しい名前'})