เนื้อหานี้ต่อจากตอนแรกเกี่ยวกับ component เบื้องต้น จะมาดูเกี่ยวกับ
การกำหนด property หรือ props ซึ่งในขั้นตอนการเรียกใช้งาน เรา
ต้องการให้คอมโพเนนท์รองรับค่าจากการส่งเข้ามาเพื่อใช้งาน เพื่อให้เห็นภาพ
จะยกตัวอย่างโค้ดจากตอนที่แล้วเข้ามาประกอบการอธิบาย
การกำหนด Props ให้กับ Component
ใน ButtonCounter คอมโพเนนท์ในตอนที่แล้ว เราทำการนับจำนวนการคลิก
ซึ่งนับจาก 0 และเพิ่มทีละ 1 ค่า แต่สมมติว่า เราต้องการให้คอมโพเนนท์นี้ รองรับให้
สามารถกำหนดค่าเริ่มต้น และกำหนด step ของการเพิ่มค่าได้ เช่น แทนที่จะเป็น
บวกเพิ่มทีละ 1 ค่า ก็สามารถบวกตามที่ค่าที่กำหนดเข้ามาได้ เราสามารถทำได้ดังนี้
รูปแบบเดิม ButtonCounter.vue
<script setup> import { ref } from 'vue' const count = ref(0) </script> <template> <button @click="count++"> You clicked me {{ count }} times. </button> </template>
ปรับเป็นดังนี้ ButtonCounter.vue
<script setup> import { ref } from 'vue' const props = defineProps({ start: { type: Number, required: true, default: 0, validator: value => value >= 0 }, step:{ type: Number, default: 1, validator: value => value >= 1 }, }) const count = ref(props.start) function increment(){ count.value +=props.step } </script> <template> <button @click="increment"> You clicked me {{ count }} times. </button> </template>
defineProps เป็นคำสั่ง macro ใน vuejs ที่มีใช้ในส่วนของ <script setup> เพื่อทำให้การ
เขียนโค้ดง่ายขึ้นและกระชับขึ้น โดยการลดส่วนโค้ดที่ซ้ำซ้อน macro ที่เด่นชัดใน VueJS ได้แก่
defineProps, defineEmits, และ defineExpose.
การใช้งาน defineProps กำหนดได้ทั้งแบบ array หรือ object
// using Array syntax const props = defineProps(['foo', 'bar']) // using Object syntax const props = defineProps({ foo: String, bar: { type: Number, required: true } })
หาก props ที่เรากำหนดเป็นข้อมูลแบบ String หรือข้อความ เราสามารถกำหนดในรูปแบบ
array ได้จะสะดวกกว่า แต่ถ้าเป็นตัวเลข หรือแบบที่ซับซ้อนกว่า หรือต้องการกำหนดเงื่อนไขเพิ่ม
เติม เช่น กำหนดชนิดข้อมูล มีค่าเริ่มต้น หรือกำหนดว่าต้อง ใช้งานหรือมี props นี้ทุกครั้งที่
เรียกใช้ หรือกำหนดการตรวจสอบข้อมูลว่าถูกต้องหรือไม่ เช่น เป็นค่ามากกว่าหรือเท่ากับ 0
แบบนี้เราจะใช้เป็นการกำหนดแบบ object ในตัวอย่าง เรากำหนดให้ทั้ง start และ step
เป็นตัวเลข และมีค่าเริ่มต้นเท่ากับ 0 และ 1 ตามลำดับ และเงื่อนไขการตรวจสอบ ต้องมากกว่า
หรือเท่ากับ 0 สำหรับ start และต้องมากกว่าหรือเท่า 1 สำหรับ step
เมื่อเรากำหนดเสร็จแล้ว ในขั้นตอนการเรียกใช้งาน เราก็จะปรับไฟล์โค้ดใน App.vue เป็นดังนี้
App.vue
<script setup> import ButtonCounter from './components/ButtonCounter.vue' </script> <template> <h1>Here are many child components!</h1> <ButtonCounter :start="2" :step="3" /> <ButtonCounter :start="0" /> </template>
จะเห็นว่าเมื่อเรากำหนดว่า start ต้องมีเสมอ ทำให้ตัวที่สอง ถึงค่า start จะเป็น 0 แต่เราก็จำเป็น
จะต้องเพิ่ม props เข้าไป ถึงแม้ว่าเราไม่เพิ่ม แต่โค้ดก็ยังทำงานปกติ จะมีแค่แจ้งเตือนใน console
ว่า [Vue warn]: Missing required prop: "start" ทั้งนี้ก็เพราะเรากำหนดค่าเริ่มต้นไว้ทั้ง
start และ step และไม่ได้กำหนดการตรวจสอบการจัดการไว้ สมมติว่าถ้าเรากำหนดการตรวจสอบ
การจัดการไว้เป็นดังนี้
ButtonCounter.vue
ButtonCounter.vue
<script setup> import { ref } from 'vue' const props = defineProps({ start: { type: Number, required: true, validator: value => value >= 0 }, step:{ type: Number, default: 1, validator: value => value >= 1 }, }) // ตรวจสอบว่า prop 'start' ถูกส่งมาหรือไม่ if (props.start === undefined) { throw new Error('Missing required prop: "start"'); } const count = ref(props.start) function increment(){ count.value +=props.step } </script> <template> <button @click="increment"> You clicked me {{ count }} times. </button> </template>
เรากำหนดให้ start จำเป็นต้องมี และไม่กำหนดค่าเริ่มต้น และให้มีการตรวจสอบว่ามีการส่งค่า
มาหรือไม่ ถ้าไม่มีการส่งค่ามาก็จะให้แจ้ง error ออกไป สมมติเราใช้งานเป็นดังนี้
App.vue
<script setup> import ButtonCounter from './components/ButtonCounter.vue' </script> <template> <h1>Here are many child components!</h1> <ButtonCounter :start="2" :step="3" /> <ButtonCounter /> </template>
ผลลัพธ์ที่ได้
มี error เกิดขึ้นและ คอมโพเนนท์ตัวที่สองไม่ถูกนำมาแสดง เพราะไม่ได้กำหนด props ชื่อ
start เข้าไปตามรูปแบบที่กำหนดไว้
จะสังเกตเห็นว่า required และ validator ใน defineProps จะเป็นการกำหนดให้เราเห็น
ชัดว่ารูปแบบที่ต้องการเป็นอย่างเท่านั้น แต่ในเรื่องของการตรวจสอบ เราจำเป็นต้องสร้างเงื่อนไข
เพิ่มเติมเข้าไป หากต้องการตรวจสอบที่มากขึ้น เช่น
// ตรวจสอบว่า prop 'start' ถูกส่งมาหรือไม่และผ่านการ validate หรือไม่ if (props.start === undefined || !props.start >= 0) { throw new Error('Invalid prop: "start" must be a number greater than or equal to 0'); }
ดังนั้นเมื่อมีการนำไปใช้งานการกำหนดในรูปแบบ object เราต้องตรวจสอบให้ถูกต้อง
อีกข้อสังเกตหนึ่งคือ โดยทั่วไป เวลากำหนด props ต่างๆ หรือ attribute ต่างๆ ใน html จะเป็น
การส่งข้อมูลเป็น string เข้าไป ซึ่งถ้าเราใช้รูปแบบเป็น
<ButtonCounter start="2" step="3" />
จะกลายเป็นว่า เลข 2 และ 3 จะเป็นข้อมูล String ซึ่งไม่ตรงตามที่เรากำหนด ทำให้การทำงาน
อาจจะผิดพลาดได้ ด้วยเหตุนี้ กรณีที่ props ของเราไม่ใช่ข้อความหรือ String เราจึงใช้วิธีการ
เรียกใช้งาน v-bind directive ในรูปแบบ v-bind:start="" หรือ เขียนแบบย่อๆ เป็น :start=""
ซึ่งจะทำให้ vue ทำการตรวจสอบและแปลงข้อมูลเลข 2 กับ 3 ไปเป็นตัวเลข แทนที่จะเป็นข้อความ
เพื่อให้ตรงกับรูปแบบ props ที่เรากำหนดในคอมโพเนนท์ เราจึงนิยมใช้แบบนี้แทน
<ButtonCounter :start="2" :step="3" />
ดูเพิ่มอีกตัวอย่าง
ให้เราสร้างคอมโพเนนท์ชื่อ BlogPost.vue ใน src > components ด้วยรูปแบบอย่างง่าย
และกำหนด props เข้าไปดังนี้
BlogPost.vue
<script setup> defineProps(['title']) </script> <template> <h4>{{ title }}</h4> </template>
และเรียกใช้งานในไฟล์ App.vue ดังนี้
App.vue
App.vue
<script setup> import ButtonCounter from './components/ButtonCounter.vue' import BlogPost from './components/BlogPost.vue' </script> <template> <h1>Here are many child components!</h1> <ButtonCounter :start="2" :step="3" /> <ButtonCounter /> <BlogPost title="My journey with Vue" /> <BlogPost title="Blogging with Vue" /> <BlogPost title="Why Vue is so fun" /> </template>
ผลลัพธ์ที่ได้
สมมติเรามีข้อมูล และต้องการวนลูปแสดงด้วย v-for โดยใช้งานร่วมกับ BlogPost คอมโพเนนท์
ก็สามารถใช้งานได้ดังนี้
App.vue
App.vue
<script setup> import { ref } from 'vue' import ButtonCounter from './components/ButtonCounter.vue' import BlogPost from './components/BlogPost.vue' const posts = ref([ { id: 1, title: 'My journey with Vue' }, { id: 2, title: 'Blogging with Vue' }, { id: 3, title: 'Why Vue is so fun' } ]) </script> <template> <h1>Here are many child components!</h1> <ButtonCounter :start="2" :step="3" /> <ButtonCounter /> <BlogPost v-for="post in posts" :key="post.id" :title="post.title" /> </template>
ผลลัพธ์ที่ได้ก็จะเหมือนกับรูปตัวอย่างก่อนหน้าด้านบน
การกำหนด Event ให้กับ Component
ใน ButtonCounter เราเรียกใช้งาน onclick event เพื่อทำการเพิ่มจำนวนการคลิก แต่
เมื่อนำคอมโพเนนท์มาใช้ใน parent เราต้องการให้คอมโพเนนท์นั้นๆ มี event ของตัวเอง
เพื่อให้สามารถนำมาใช้งาน หรือสามารถเรียกใช้ event ของ คอมโพเนนท์ได้ เราต้องทำการ
กำหนด event ให้กับคอมโพเนนท์นั้น โดยสามารถทำได้ดังนี้
ButtonCounter.vue
ButtonCounter.vue
<script setup> import { ref } from 'vue' const props = defineProps({ start: { type: Number, default: 0 }, step:{ type: Number, default: 1 }, }) // กำหนด event ที่จะปล่อยไปกับคอมโพเนนท์ const emit = defineEmits(['increment','reset']) const count = ref(props.start) function increment(){ count.value +=props.step // ปล่อย increment โดยส่งค่าข้อมูลออกไปด้วย emit('increment', count.value) } function resetstart(){ count.value = props.start // ปล่อย reset โดยไม่ส่งค่าข้อมูลออกไป emit('reset') } </script> <template> <button @click="increment" @dblclick="resetstart" > You clicked me {{ count }} times. </button> </template>
เริ่มต้นที่เรากำหนดชื่อ event ที่ต้องการ
// กำหนด event ที่จะปล่อยไปกับคอมโพเนนท์ const emit = defineEmits(['increment','reset'])
จากนั้นส่งออกค่าในคำสั่ง หรือเหตุการณ์ ที่ต้องการ
function increment(){ // ปล่อย increment โดยส่งค่าข้อมูลออกไปด้วย emit('increment', count.value) } function resetstart(){ // ปล่อย reset โดยไม่ส่งค่าข้อมูลออกไป emit('reset') }
สังเกตการทำงานใน template ของคอมโพเนนท์
<button @click="increment" @dblclick="resetstart" > You clicked me {{ count }} times. </button>
เรากำหนดว่า เมื่อคลิก ให้ทำคำสั่ง increment() คือเพิ่มจำนวน และเมื่อเพิ่มจำนวนแล้ว
ก็ให้ปล่อย event ที่ชื่อว่า increment ออกไป พร้อมกับค่าข้อมูล count ณ ปัจจุบัน
ในขณะที่ เมื่อกดดับเบิลคลิก ให้ทำคำสั่ง resetstart() คือให้ค่าเริ่มต้นของ count กลับ
มาเป็นค่าเริ่มต้น ตามที่กำหนดตอนแรก และเมื่อรีเซ็ตค่าแล้ว ก็ให้ปล่อย event ที่ชื่อว่า
reset ออกไป โดยไม่มีการส่งค่าข้อมูลไปด้วย
สรุปการทำงานก็คือ คลิก 1 ครั้งเป็นการเพิ่มจำนวน กดดับเบิลคลิกหรือสองครั้งติดกัน จะเป็น
การรีเซ็ตค่าการนับใหม่เป็นค่าเริ่มต้น
เมื่อกำหนด event ให้กับคอมโพเนนท์แล้ว เราก็สามารถเรียกใช้งานในส่วนของ parent
ได้เป็นดังนี้
App.vue
App.vue
<script setup> import { ref } from 'vue' import ButtonCounter from './components/ButtonCounter.vue' import BlogPost from './components/BlogPost.vue' const posts = ref([ { id: 1, title: 'My journey with Vue' }, { id: 2, title: 'Blogging with Vue' }, { id: 3, title: 'Why Vue is so fun' } ]) function countupdate(newValue){ console.log('Increment '+ newValue) } function countreset(){ console.log('Reset ') } </script> <template> <h1>Here are many child components!</h1> <ButtonCounter :start="2" :step="3" /> <ButtonCounter @reset="countreset" @increment="countupdate" /> <BlogPost v-for="post in posts" :key="post.id" :title="post.title" /> </template>
ดูในส่วนของการเรียกใช้งานที่ปุ่ม ButtonCounter
<ButtonCounter @reset="countreset" @increment="countupdate" />
จะเห็นรูปแบบการใช้งาน event โดยใช้ v-on:increment กับ v-on:reset ซึ่งเขียนแบบ
ย่อได้เป็น :increment="" กับ :reset="" ในตัวอย่าง เมื่อเกิด event ขึ้น เรากำหนดให้
ไปทำคำสั่ง countupdate และ countreset โดย countupdate จะรองรับข้อมูลที่ส่ง
มาพร้อมกับ event ด้วย
function countupdate(newValue){ console.log('Increment '+ newValue) } function countreset(){ console.log('Reset ') }
จะเห็นว่ารูปแบบการใช้งาน event ที่เราสร้างขึ้น ก็จะสามารถใช้งานได้คล้ายกับ DOM element
ปกติที่เราคุ้ยเคย
เนื้อหาเกี่ยวกับการกำหนด props และ event รวมถึงการใช้งาน props และ event ที่กำหนด
ใน component เมื่อนำมาใช้งานใน parent ก็จะขอจบประมาณนี้ เนื้อหาเกี่ยวกับคอมโพเนนท์
เบื้องต้นยังมีต่อ รอติดตามในตอนที่ 3