ใครที่ยังไม่ได้อ่านตอน 1 แนะนำให้กลับไปอ่านก่อนครับจะได้เข้าใจการ emit event กลับมาด้วย ส่วนใครที่อ่านแล้วมาลุยกันต่อเลยครับ
ในการกด click แล้วให้เพิ่ม component เข้าไปคงหนีไม่พ้นการใช้ array และ v-for เข้าช่วยเมื่อ click ก็ทำการ push array เข้าไป v-for ก็จะทำการเพิ่ม component เอง
เรามาแก้ไขไฟล์ TodoList.vue กัน
<template>
<div class="container">
<div class="field-warp">
<field-list v-for="(list, index) in lists"
:key="list.key"
@update:title="value => list.title = value"
@update:description="value => list.description = value"
:title="list.title"
:description="list.description"></field-list>
</div>
<pre>{{ lists }}</pre>
<button type="button" class="btn-add" @click="addField">Add Field</button>
</div>
</template>
<script>
import FieldList from './FieldList'
export default {
components: {
FieldList
},
data() {
return {
lists: [
{ key: 0, title: '', description: '' }
],
indexKey: 0
}
},
methods: {
addField() {
const index = this.indexKey += 1
this.lists.push({ key: index, title: '', description: '' })
}
}
}
</script>
<style>
.container {
width: 960px;
margin: 0 auto;
text-align: center;
}
.field-warp {
display: block;
}
.btn-add {
margin-top: 30px;
}
</style>
เรามาแยกการทำงานทีละส่วนของไฟล์ TodoList.vue กันเริ่มจาก
<field-list v-for="(list, index) in lists"
:key="index"
@update:title="value => list.title = value"
@update:description="value => list.description = value"
:title="list.title"
:description="list.description"></field-list>
v-for ทำการ loop ตัวแปล lists แต่ละตัวแยกออกมาโดยในแต่ละรอบจะมี list และ index แยกจากกัน
:key ทำการส่ง index เข้าไปเพื่อนกำหนด key ให้กับตัว component
**** อัพเดท ***
คำเตือนในส่วนของ :key ที่ใช้ค่า index ส่งไปควรสร้าง indexKey แล้วให้รัน index ด้วยตัวเองเพราะเมื่อมีการสั่งลบ array lists ตัว key จะ render ใหม่และจะทับกับของเดิมเช่นตัวเก่ามี 1,2,3 เราจะทำการลบ 2 ออกจะเหลือ 1,3 ตัว lists จะ render ใหม่แต่เลข index ตัวที่ 3 ขยับมาเป็น 2 ทำให้ vuejs คิดว่าเราต้องการลบตัวที่ 3 แทนครับเพราะรู้สึกว่า dataจะอัพเดทก่อนแล้ว vuejs ค่อยไปลบ dom ทำให้ index เลื่อนจึงลบผิด dom ครับ
@update:title=”value => list.title = value” เมื่อมี event ชื่อ update:title เกิดขึ้นให้เอาค่าตัวแปร value ใส่เข้าไปใน list.title ส่วน event ชื่อ update:description ก็ทำเช่นเดียวกันโดยใส่ไปใน list.description โดย list แต่ละตัวจะมาจาก array lists ที่มี Object อยู่ข้างใน
:title and :description เป็นการส่ง props เข้าไปใน component FieldList.vue แบบปกติที่เราคุ้นเคย (ไม่ต้องส่งก็ได้ในกรณีที่เราไม่ต้องการ initial ค่าอะไรลงไปในตอนแรก)
<button type="button" class="btn-add" @click="addField">Add Field</button>
เมื่อกดปุ่มนี้จะเรียกฟังก์ชั่น addField
methods: {
addField() {
this.lists.push({ title: '', description: '' })
}
}
}
addField จะทำการ push array เข้าไปโดย initial ค่าเป็น Object ที่มี element เป็น title และ description เป็นค่าว่าง
<template>
<div>
<input type="text" :value="title" placeholder="Title" @input="updateValue($event.target.value, 'title')">
<input type="text" :value="description" placeholder="Description" @input="updateValue($event.target.value, 'description')">
</div>
</template>
<script>
export default {
props: ['title', 'description'],
methods: {
updateValue(value, type) {
if (type === 'title') {
this.$emit('update:title', value)
return
}
this.$emit('update:description', value)
}
}
}
</script>
การทำงานแต่ละส่วนของไฟล์ FieldList.vue
props: ['title', 'description']
ทำการรับ props ที่ส่งเข้ามาและนำไปใส่ใน attribute value ของ input
<input type="text" :value="title" placeholder="Title" @input="updateValue($event.target.value, 'title')">
<input type="text" :value="description" placeholder="Description" @input="updateValue($event.target.value, 'description')">
@input=”updateValue($event.target.value, ‘title’)” เมื่อมีการพิมพ์ค่าใน input จะทำการเรียกฟังก์ชั่น updateValue ส่ง param เข้าไปใน function 2 ตัวคือ
$event เป็น event ของตัว vue ลอง console.log() ออกมาดูได้ครับสามารถเอาไปต่อยอดได้หลายอย่างเลยแต่ผมเข้าไปเอาข้อมูลแค่ target.value เพื่อเอาค่าจาก input เท่านั้น
type อันนี้เป็นการส่งไปบอกเฉยๆว่าให้ emmit update ตัวไหนกลับไปผมแค่ไม่อยากแยกฟังก์ชั่นเลยเขียน if เอาเพื่อเลือกการส่ง emit
methods: {
updateValue(value, type) {
if (type === 'title') {
this.$emit('update:title', value)
return
}
this.$emit('update:description', value)
}
}
ถ้า type เป็น title ก็ให้ emit update:title ตรงตัวเลยครับแล้วส่ง value กลับไป
เอาละมาถึงตอนสุดท้ายทุกครั้งที่เราพิมพ์ข้อมูลลงไป emit จะส่งค่ากลับไปเข้า object ตาม index ที่ได้ loop ไว้ครับทำให้ข้อมูลทั้งหมดไม่ทับกันซึ่งถ้าเราใช้ v-modal แบบปกติจะทำให้คืนค่ากลับมาทับกันไม่ได้แยกออกจากกันเป็นส่วนๆแบบนี้ มีรูปภาพประกอบ…
มาถึงตรงนี้แล้วหลายคนอ๋อแน่นอนว่าเห้ยมันทำแบบนี้ได้ด้วยซึ่งผมก็ไม่ได้คิดแบบนี้เหมือนกันครับตอนแรกว่าใช้ loop แล้วเอาแต่ละ index มาทำแบบนี้ต้องขอบคุณกลุ่ม Vue.js Thailand ที่มาสะกิตต่อมอยากรู้อยากเห็นของผมด้วย..
*** แก้ไขการส่ง :key เข้าไปใน component ในกรณีที่ใช้ v-for ในท่าประมานนี้ควรสร้าง index ขึ้นมารันเองนะครับคำอธิบายอยู่ด้านบนตรงไฟล์ TodoList.vue ครับ
ลิงค์ git ลอง clone ไปดูได้เลยครับ
tutorial-two-way-binding-vuejs (this link opens in a new window) by anthoz69 (this link opens in a new window)
tutorial-two-way-binding-vuejs. This project will show you how to pass data from child to parent component.
Reference
https://vuejs.org/v2/guide/components.html#sync-Modifier
https://vuejs.org/v2/guide/components.html#Form-Input-Components-using-Custom-Events
https://vuejs.org/v2/guide/components.html#Customizing-Component-v-model
https://www.facebook.com/groups/VuejsThailand/permalink/318354791957118/