コンポーネント編 第一部
お題はこちらです。
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関係が残っていますね.