初心者がVue.jsの公式ガイドを勉強するメモ コンポーネント編　第一部

# コンポーネント編　第一部
## お題はこちらです。

### https://jp.vuejs.org/v2/guide/components.html


* ブラウザー結果は、掲載しませんので、実際に確認してみてください。
* この章は、ボリュームがあります。
* コンポーネントは、Vue.jsの強力な機能だと思いますが、初心者は、まず使わない機能だとも思います。　初心者編として少し矛盾する気もしますが、初心者でも色々な初心者がいると思いますので、必要と思っている初心者向けに書きたいと思います。　少しづつでも機能を使うことが、次のステップに上がるのに必要と感じています。
* 主に、slot関係以下が、第二部になります。

## グローバル登録

* App.vue

``` html

<template lang="pug">
  #app
    my-component

</template>
<script>

import Vue from 'vue'

const MyComponent = {
  template: `<div> Custom component</div>`
}
Vue.component( MyComponent )
// Vue.component('my-component', MyComponent)

export default {
  name: 'app',
  data () {
    return {
      checked: '',
      checkedNames: [],
    }
  },
  mounted () {
  },
  components: {
    'my-component': MyComponent
  }
}

</script>

```
## ローカル登録

* App.vue

``` html
<template lang="pug">
  #app
    my-component

</template>

<script>

var Child = {
  template: '<div> Local Custom Component </div>'
}

export default {
  name: 'app',
  data () {
    return {
    }
  },
  components: {
    'my-component': Child
  }
}

</script>
```
## DOM テンプレート解析の注意事項
* index.htmlのみの環境で影響を受けます。　文字列テンプレートでは無いです。

* node環境は文字列テンプレートです。　こちらを使いましょう。


``` html
<template lang="pug">
  #app
    local-component
    local-component
    local-component

    div -------------------------------

    global-component
    global-component
    global-component

</template>

<script>

import Vue from 'vue'

const GlobalComponent = {
  template: `<button @click="counter += 1">{{ counter }}</button>`,
  data: function () {
    return data
  }
}
Vue.component( 'global-component', GlobalComponent )

var data = { counter: 0 }
var LocalComponent = {
  template: '<button @click="counter += 1">{{ counter }}</button>',
  data: function () {
    return data
  }
 }

export default {
  name: 'app',
  data () {
    return {
    }
  },
  components: {
    'localComponent': LocalComponent
  }
}

</script>

```
* カウンターは、参照値で同一の値を参照していることを確認.


* App.vue

``` html
<template lang="pug">
  #app
    local-component
    local-component
    local-component

</template>

<script>
var Child = {
  template: '<button @click="counter += 1">{{ counter }}</button>',
  data: function () {
    return {
      counter: 0
    }
  }
 }

export default {
  name: 'app',
  data () {
    return {
    }
  },
  components: {
    'localComponent': Child
  }
}

</script>
```
* カウンターは個別になるのを確認



## プロパティ

### プロパティによるデータの伝達

* App.vue

``` html
<template lang="pug">
  #app
    globalChild(message="hello")
    div --------------------------
    localComponent(message="localHello")

</template>

<script>
import Vue from 'vue'

Vue.component('globalChild', {
  props: ['message'],
  template: '<span>{{ message }}</span>'
})

var Child = {
  template: '<span>{{ message }}</span>',
  props: ['message'],
  data () {
    return {
    }
  }
 }

export default {
  name: 'app',
  data () {
    return {
    }
  },
  components: {
    'localComponent': Child
  }
}

</script>
```
* 慣れるまでは、両方のコンポーネント記述します。
* 面倒になったら、片方にします。(笑)

---
* コンポーネント内と外とのデータの受け渡しが厳密なことに、注目してください。
* コンポーネントを、App.vueに書いていますが、別ファイルにしたほうがより、汎用的です。
* コンポーネントは、本来たくさんの場所から呼び出される、雛形ファイルなので、データの受け渡しが厳密だと解釈するとイメージし易いかと思います。

---

### キャメルケース vs ケバブケース

* App.vue

``` html
<template lang="pug">
  #app
    globalChild(my-message="hello")
    globalChild(myMessage="hello")
    div --------------------------
    localComponent(my-message="localHello")
    localComponent(myMessage="localHello")

</template>

<script>
import Vue from 'vue'

Vue.component('globalChild', {
  props: ['myMessage'],
  template: '<span> {{ myMessage }}</span>'
})

var Child = {
  template: '<span> {{ myMessage }}</span>',
  props: ['myMessage'],
  data () {
    return {
    }
  }
 }

export default {
  name: 'app',
  data () {
    return {
    }
  },
  components: {
    'localComponent': Child
  }
}

</script>
```
* node環境で記載する場合、どちらで書いても構いません。
* 独自タグもnode環境であれば、どちらでもかまいません。例はcamel caseしかありませんが。。。

### 動的なプロパティ

* App.vue

``` html
<template lang="pug">
  #app
    input(v-model="text")
    global-child(:myMessage="text")
    localComponent(:myMessage="text")

</template>

<script>
import Vue from 'vue'

Vue.component('globalChild', {
  props: ['myMessage'],
  template: '<span> {{ myMessage }}</span>'
})

var Child = {
  template: '<span> {{ myMessage }}</span>',
  props: ['myMessage'],
  data () {
    return {
    }
  }
 }

export default {
  name: 'app',
  data () {
    return {
      text: 'sample'
    }
  },
  components: {
    'localComponent': Child
  }
}

</script>
```
* 省略記号を使っていますが、慣れるまでは`v-bind:myMessage`としたほうがいいかもしれません。
* 最初は、変数（key）なのか文字列なのか判りにくいです。
* 親要素のdataをpropsを使って、引き渡しています。 バインド先は、各コンポーネントです。

### v-bind:プロパティ名 の代わりに v-bind

* App.vue

``` html
<template lang="pug">
  #app
    globalChild(:todo="todoObj")
    div ----------------------------
    localComponent(
      :text="todoObj.text"
      :isComplete="todoObj.isComplete"
    )

</template>

<script>
import Vue from 'vue'

Vue.component('globalChild', {
  props: ['todo'],
  template: '<span> {{ todo.text }} : {{ todo.isComplete}}</span>'
})

var Child = {
  template: '<span> {{ text }} : {{ isComplete }}</span>',
  props: ['text', 'isComplete'],
  data () {
    return {
    }
  }
 }

export default {
  name: 'app',
  data () {
    return {
      todoObj: {
        text: 'Learn Vue',
        isComplete: false
      }
    }
  },
  components: {
    'localComponent': Child
  }
}

</script>

```
* ~~引数なし、動きません！ だれか教えて下さい. Question 02~~
* 一応、todoを引数にすれば、todoObjの塊として渡せてますが、引数ついてるし、違うということですよね。


* App.vue

``` html
<template lang="pug">
  #app
    globalChild(v-bind="todoObj")
    div ----------------------------
    localComponent(
      :text="todoObj.text"
      :isComplete="todoObj.isComplete"
    )

</template>

<script>
import Vue from 'vue'

Vue.component('globalChild', {
  props: ['text', 'isComplete'],
  template: '<span> {{ text }} : {{ isComplete}}</span>'
})

var Child = {
  template: '<span> {{ text }} : {{ isComplete }}</span>',
  props: ['text', 'isComplete'],
  data () {
    return {
    }
  }
 }
export default {
  name: 'app',
  data () {
    return {
      todoObj: {
        text: 'Learn Vue',
        isComplete: false
      }
    }
  },
  components: {
    'localComponent': Child
  }
}

</script>
```
* 自己解決しました。 (分割代入と同じ理屈です)
* 親要素のdata propertyを塊で取得して、必要なプロパティのみ使う感じです。
* スッキリ書けるので、いいですね。
* 上の書き方も有りかと思うので、そのまま追記します。


### リテラル vs 動的

* App.vue

``` html
<template lang="pug">
  #app
    globalChild(:some-prop="1")
    globalChild(some-prop="1")

    div --------------------------

    localComponent(:someProp="2")
    localComponent(someProp="2")

</template>

<script>
import Vue from 'vue'

Vue.component('globalChild', {
  props: ['someProp'],
  template: '<span>Number? {{ someProp + 1 }} </span>'
})

var Child = {
  template: '<span>Number? {{ someProp + 1 }}</span>',
  props: ['someProp'],
  data () {
    return {
    }
  }
 }

export default {
  name: 'app',
  data () {
    return {
    }
  },
  components: {
    'localComponent': Child
  }
}

</script>
```
* v-bind付きは、変数と思っていましたが、数字を渡すと数字として引き渡されんですね。
* 仕組みはJSの式なんですね。
* これは、やってしまいそうな間違いです。

### 単方向データフロー

* App.vue

``` html

<template lang="pug">
  #app
    | ParentData: {{ parentData }}
    globalChild(v-bind="parentData")

    div --------------------------

    localComponent(:size=" parentData.size ")

</template>

<script>
import Vue from 'vue'

Vue.component('globalChild', {
  props: ['parentNumber'],
  template: `
    <div>
      <div>Case_1 - ParentNumber: {{ parentNumber }}</div>
      <div>Case_1 - Counter: {{ counter }}</div>
      <button @click="parentNumber = 2">direct write</button>
    </div>
  `,
  data () {
    return {
      counter: this.parentNumber,
    }
  },
  computed: {
  }
})

var Child = {
  template: `
    <div>
      <div>Case_2: {{ size }}{{ size }}</div>
      <div>Case_2: {{ normalizeSize }}{{ normalizeSize }}</div>
      <button @click="size = 'BBB'">direct write</button>
    </div>
  `,
  props: ['size'],
  data () {
    return {
    }
  },
  computed: {
    normalizeSize () {
      return this.size.trim().toLowerCase()
    }
  }
 }

export default {
  name: 'app',
  data () {
    return {
      parentData: {
        parentNumber: 1,
        size: 'AAA'
      }
    }
  },
  components: {
    'localComponent': Child
  }
}

</script>

```
* Case_1 は、親から貰った、値をコンポーネントの初期値にする場合
* Case_2 は、親から貰った、値を加工して使う場合です。
* 注意書きは、親から貰った値が配列、オブジェクトであった場合、そのデータを書き換えると親データが書き換えられるので、一旦置き換えて使ってくださいとのことです。
* もちろん、表示データとしてなら、そのまま使えばいいです。
* 試しに書き換えようとすると、コンソールにエラーがでますね。
  * コンポーネント側は書き換わりますが、親側には影響無かったです。
  * いずれにしても、propsの値をコンポーネント側で書き換えるのはNGということです。


### プロパティ検証
* 受け取るプロパティを制限できる見たいです。
* 初心者は、まず数字と文字列の制限から始めるのもいいですね。


### プロパティではない属性
* 飛ばします

## カスタムイベント

### カスタムイベントとの v-on の使用

* App.vue

``` html
<template lang="pug">
  #app
    p {{ total }}
    button-counter(@increment="incrementTotal")
    button-counter(@increment="incrementTotal")

</template>

<script>
import Vue from 'vue'

Vue.component('button-counter', {
  template: `<button @click="incrementCounter"> {{ counter }} </button>
  `,
  data () {
    return {
      counter: 0,
    }
  },
  methods : {
    incrementCounter () {
      this.counter += 1
      this.$emit('increment')
    }
  }
})

export default {
  name: 'app',
  data () {
    return {
      total: 0
    }
  },
  methods: {
    incrementTotal () {
      this.total += 1
    }
  },
  components: {
  }
}

</script>
```
* 基本動作ですが、慣れていないと少し複雑な感じをうけますね。
* 親と子コンポーネントは完全に別処理であることを確認。
* `@increment="incrementTotal` でemitが実行されたタイミングで親側関数が呼ばれます。

### ペイロードのデータ利用
* コンポーネントのデータを親のdataに追加

```html
<template lang="pug">
  #app
    p(v-for="msg in messages") {{ msg }}
    button-message(@message="handleMessage")

</template>

<script>
import Vue from 'vue'

Vue.component('button-message', {
  template: `<div>
    <input type="text" v-model="message"/>
    <button @click="handleMessage"> Send </button>
  </div>`,
  data () {
    return {
      message: 'component Message'
    }
  },
  methods : {
    handleMessage () {
      this.$emit('message', { message: this.message })
    }
  }
})

export default {
  name: 'app',
  data: () => ({
    messages: ['parent 1', 'parent 2']
    }),
  methods: {
    handleMessage (payload) {
      this.messages.push(payload.message)
    }
  },
  components: {
  }
}

</script>
```
* オブジェクトでデータを渡しています。

### ネイティブイベントとコンポーネントのバインディング
* コンポーネントでは、v-onは、子要素からのemitの監視に使いますが、例の場合、親要素のクリックイベントとして使いたい場合は、native修飾子を使うと、親側のイベントとして動作します。

-----

### .sync 修飾子

* App.vue

``` html

<template lang="pug">
  #app
    div {{ bar }}
    //- globalChild(:foo="bar" @update:foo="val => bar = val")
    globalChild(:foo.sync="bar")

</template>

<script>
import Vue from 'vue'

Vue.component('globalChild', {
  props: ['foo'],
  template: `
    <div>
      <button @click="aaa">click</button>
      <input type="text" v-model="text" />
    </div>
  `,
  data () {
    return {
      text: ''
    }
  },
  methods: {
    aaa () {
      this.$emit('update:foo', this.text)
    }
  }
})

export default {
  name: 'app',
  data () {
    return {
      bar: 'ParentValue'
    }
  },
  components: {
  }
}

</script>


```
* v-on は使っていません, emitが実行され、値がupdateしています。
* コンポーネント側のdata プロパティを親のdataプロパティとをクリックイベントで同期させています。
* コメントで消しているテンプレートでも、同じ動作です。
* 親側の処理が省略出来る感じですね。

## カスタムイベントを使用したフォーム入力コンポーネント

* App.vue

``` html
<template lang="pug">
  #app
    div {{ something }}
    input(v-model="something")

    div {{ something2 }}
    input(
      v-bind:value="something2"
      v-on:input="something2 = $event.target.value"
    )

</template>

<script>

export default {
  name: 'app',
  data () {
    return {
      something: 'v-model',
      something2: 'v-bind v-on',
    }
  },
  components: {
  }
}

</script>

```
* まずは、コンポーネントを使っていない例です。
* 結果は、同じです。

#### component

* App.vue

```html

<template lang="pug">
  #app
    | ParentValue : {{ parent }}
    div
      custom-input(:input.sync="parent")

</template>

<script>

import Vue from 'vue'

Vue.component('custom-input', {
  template: `
    <span>
      <input
        :value="value"
        @input="updateValue($event.target.value)"
      />
    </span>
  `,
  props: ['value'],
  methods: {
    updateValue (value) {
      this.$emit('update:input', value)
    }
  }
  })

export default {
  name: 'app',
  data () {
    return {
      parent: 'Parent'
    }
  },
  components: {
  }
}
</script>
```
* .sync修飾子は、このあたりが楽できますね。

#### exapmple
* App.vue

``` html
<template lang="pug">
  #app
    div {{ price }}
    currency-input(v-model="price")

</template>

<script>

import Vue from 'vue'

Vue.component('currency-input', {
  template: `
    <span>
      <input
        ref="input"
        v-bind:value="value"
        v-on:input="updateValue($event.target.value)"
      />
    </span>
  `,
  props: ['value'],
  methods: {
    updateValue (value) {
      let formattedValue = value
      .trim()
      .slice(
        0,
        value.indexOf('.') === -1
          ? value.length
          : value.indexOf('.') + 3
        )
        if (formattedValue !== value) {
          this.$refs.input.value = formattedValue
        }
        this.$emit('input', Number(formattedValue))
    }
  }
  })

export default {
  name: 'app',
  data () {
    return {
      price: ''
    }
  },
  components: {
  }
}
</script>
```

* v-model を分解した、v-bindとv-on と見比べるとなんとなく、連携が見えて来る感じがします。
* value -> props, emit -> input で受け渡しですね。
* ボリュームありますので、第一部ということで、今回はここまでにします。
* 主にslot関係が残っていますね.