LoginSignup
7
9

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-02-02

コンポーネント編 第一部

お題はこちらです。

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

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

グローバル登録

  • App.vue

<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
<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環境は文字列テンプレートです。 こちらを使いましょう。

<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

<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
<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
<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
<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
<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

<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
<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

<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
<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に追加
<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

<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
<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

<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
<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関係が残っていますね.
7
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
9