เมื่อไม่นานมานี้ Vue 2 ก็มีอายุครบ 1 ขวบพอดิบพอดีทางทีมของ Vue ก็ได้ออก Style Guide (BETA) มาเพื่อแนะแนวทางการเขียน Vue ที่ดี และ เค้าก็ให้คำแนะนำมาอย่างดีว่าวิธีการนี้ไม่ทำตามก็ไม่เป็นไรนะเพราะให้คำนึงถึง ประสบการของทีม และ เทคโนโลยีที่ใช้ในโปรเจค รวมถึง style แต่ละคนด้วย ในบทความนี้ผมจะเลือกอันที่เด็ดๆและน่าสนใจมานำเสนอนะครับ
Vue ได้แบ่งกฏของการเขียนไว้ 4 แบบด้วยกันคือ
- Priority A: Essential สำคัญ ควรทำตามเพราะจะได้ไม่เกิด error และ ป้องกันข้อผิดพลาดยิบย่อยที่หายากมากเวลาเกิด
- Priority B: Strongly Recommended ควรทำตามอย่างยิ่ง เพราะจะทำให้อ่าน code ง่าย หาไฟล์ง่าย และ ถ้าไม่ทำตาม Dev คนอื่นอาจจะเดินมาด่าคุณได้…
- Priority C: Recommended แนะนำ ถ้าทำตามนี้คุณจะเขียน Vue ไปในทิศทางที่ Dev ส่วนใหญ่ทำกัน และ ทีมก็จะคุยง่ายเพราะว่าเขียนเหมือนกัน แถม community ก็จะเข้าใจคุณง่ายขึ้นด้วยเวลาไปถามหา Error
- Priority D: Use with Caution ใช้กับข้อควรระวัง
หลังจากเข้าใจตรงกันแล้วว่าแต่ละ กฏ Vue เค้าให้น้ำหนักยังไงบ้าง และ แต่ละหัวข้อจะมีตัวอย่าง Code ที่ Bad and Good อยู่ด้วยซึ่งผมจะเอา Code ที่ Bad ไว้ด้านบน และ Good ไว้ด้านล่างเสมอตาม Guide ของ Vue เลย
Priority A Rules: Essential (Error Prevention)
Multi-word component names
ชื่อของ component ควรจะเป็นชื่อที่มีหลายคำเพราะจะได้ไม่ชนกับ tag html เพราะ tag html นั้นจะเป็นชื่อเดี่ยวๆ เช่น <a></a> <nav></nav> เป็นต้น และ แยกชื่อด้วยเครื่องหมายลบแบบนี้ <todo-item></todo-item>
Vue.component('todo', {// ...})// or export default {name: 'Todo',// ...}
// Good
Vue.component('todo-item', {// ...})// orexport default {name: 'TodoItem',// ...}
Component data
data ใน component ควรจะเป็น function ทั้งหมด เพราะถ้าเราเขียนเป็น object ธรรมดามันจะไปแทนที่ใน instant ของ vue ที่เรา new vue() ขึ้นมาทำให้ถ้าตั้งชื่อซ้ำกันอาจจะพังไปเลยก็ได้
Vue.component('some-comp', {
data: {
foo: 'bar'
}
})// orexport default {
data: {
foo: 'bar'
}
}
// Good
Vue.component('some-comp', {
data: function () {
return {
foo: 'bar'
}
}
})// In a .vue file
export default {
data () {
return {
foo: 'bar'
}
}
}// กรณีนี้ใช้ใน instant ของ new Vue เท่านั้นครับที่ data เป็น object
new Vue({
data: {
foo: 'bar'
}
})
Prop definitions
การรับ props เราควรเขียน validate ตัว props ที่รับมาด้วยเพื่อเช็คความถูกต้องแถมยังกัดด้วยว่าการเขียน props แบบ bad นั้นใช้เฉพาะทำ prototype เฟ้ยอย่าแหลมเอามาใช้จริงกับ production
props: ['status']
// Good
props: {
status: String
}
// เช็คแบบนี้ดีที่สุดครับ
props: {
status: {
type: String,
required: true,
validate: function (value) {
return [
'syncing',
'synced',
'version-conflict',
'error'
].indexOf(value) !== -1
}
}
}
Keyed v-for
ในการใช้ v-for ทุกครั้งเราควรส่ง props :key เข้าไปใน component ด้วยเพราะ vue จะเอาไปสร้าง state ของแต่ละ component เพื่อให้จัดการอัพเดท สร้าง ลบ ตัว component ได้ถูกต้อง
<ul>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ul>
// Good
<ul>
<li
v-for="todo in todos"
:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
Private property names
ในการสร้าง mixin, plugin หรือ function อะไรก็ตามที่เรานำ reuse ได้บ่อยๆควรมีคำนำหน้า $_mixinName_… เพราะจะได้ไม่ไปซ้ำกับ library ที่เราเอาเข้ามาใช้ในโปรเจค ผมว่าอันนี้ชื่อยาวหน่อยแต่ 100% แน่นอนผมโดนมาแล้วหาแทบตาย 🙂
var myGreatMixin = {
// ...
methods: {
update: function () {
// ...
}
}
}
var myGreatMixin = {
// ...
methods: {
_update: function () {
// ...
}
}
}
var myGreatMixin = {
// ...
methods: {
$update: function () {
// ...
}
}
}
var myGreatMixin = {
// ...
methods: {
$_update: function () {
// ...
}
}
}
// Good
var myGreatMixin = {
// ...
methods: {
$_myGreatMixin_update: function () {
// ...
}
}
}
Priority B Rules: Strongly Recommended (Improving Readability)
Single-file component filename casing
การตั้งชื่อ component อันนี้เป็นอะไรที่เจ็บปวดมากเพราะถ้าเราไม่มีการตกลงที่ดีกับ Dev คนอื่นๆ และ คุยให้เข้าใจกันคุณจะหาไฟล์ของ Dev คนอื่นไม่เจอเลย T T พูดแล้วเศร้า และ เค้าแนะนำว่าควรใช้ PascalCase หรือ kebab-case เช่น MyComponent หรือ my-component
components/
|- mycomponent.vue
components/
|- myComponent.vue
// Good
components/
|- MyComponent.vue
components/
|- my-component.vue
Base component names
สำหรับ component ที่ทำหน้าที่อย่างเดียวเช่น Button, Table, Chart, Icon ควรมีชื่อนำหน้าเพื่อให้รู้ว่า component นี้มันเป็นส่วนประกอบย่อยๆนะส่วนชื่อนำหน้าอะไรไปตกลงกับ Team เด้อ
components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue
// Good
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue
Single-instance component names
Component ที่ทำหน้าที่เป็นส่วนประกอบที่ใหญ่ที่สุดควรมีชื่อนำหน้าที่เฉพาะเจาะจง และ ใช้งานครั้งเดียวไม่ได้ใช้ซ้ำ เช่น หน้า Profile ก็ใช้ The ให้มันซะเวลาคนอื่นเห็นคงร้อง อ่อทันที
components/
|- Heading.vue
|- MySidebar.vue
// Good
components/
|- TheHeading.vue
|- TheSidebar.vue
Tightly coupled component names
การตั้งชื่อที่ดีควรตั้งชื่อให้มันคล้ายคลึงกันที่สุดเช่นระบบ Profile ถ้าเราตั้งชื่อ UserProfile.vue, SearchProfileUser.vue, UserAboutProfile.vue เวลาหาก็คงงมกันนานแน่นอนเราจึงควรตั้งชื่อแบบนี้
components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue
// Good
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue
จากตัวอย่างที่ผมเขียนก็จะได้แบบนี้
UserProfile.vue
UserProfileSearch.vue
UserProfileAbout.vue
Component name casing in templates
การเขียนชื่อไฟล์ลงใน template html ควรเขียนเป็นตัวเล็กทั้งหมดและเว้นวรรคด้วยเครื่องหมายลบ
<!-- In single-file components and string templates -->
<mycomponent/>
<!-- In single-file components and string templates -->
<my-component/>
<!-- In single-file components and string templates -->
<myComponent/>
<!-- In DOM templates -->
<MyComponent></MyComponent>
// Good
<!-- In single-file components and string templates -->
<MyComponent/>
<!-- In DOM templates -->
<my-component></my-component>
Prop name casing
เราควรตั้งชื่อ props ที่จะส่งเป็น camelCase และ ใช้ kebab-case ใน template, JSX
props: {
'greeting-text': String
}
<WelcomeMessage greetingText="hi"/>
// Good
props: {
greetingText: String
}
<WelcomeMessage greeting-text="hi"/>
Complex expressions in templates
อย่างที่ทราบกันอยู่แล้วว่า Vue.js นั้นไม่แนะนำให้เราไปเขียน js หรือ การคำนวนลงใน template เค้าจึงสร้าง computed มาให้เราใช้เพื่อคำนวนค่า และ นำชื่อ computed ไปใช้จะได้อ่านใน template HTML ง่ายๆ อันนี้แนะนำว่าต้องทำ!!
{{
fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}}
// Good
<!-- In a template -->
{{ normalizedFullName }}// ย้าย expression ไปเขียนใน computed แทน
computed: {
normalizedFullName: function () {
return this.fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}
}
Complex computed properties
ในการเขียน computed เราควรเขียน expression แยกให้เยอะที่สุดเพื่อง่ายต่อการ reuse และ แก้ไข logic ในวันข้างหน้าด้วย อันนี้ Dev Vuejs ควรทำมากๆๆๆๆ
computed: {
price: function () {
var basePrice = this.manufactureCost / (1 - this.profitMargin)
return (
basePrice -
basePrice * (this.discountPercent || 0)
)
}
}
// Good
computed: {
basePrice: function () {
return this.manufactureCost / (1 - this.profitMargin)
},
discount: function () {
return this.basePrice * (this.discountPercent || 0)
},
finalPrice: function () {
return this.basePrice - this.discount
}
}
Priority C Rules: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
Empty lines in component/instance options
เวลาเราเขียน component เราควร enter เพื่อแยกบรรทัดแต่ละ property ซักนิดเราจะอ่านโค้ดง่ายขึ้นมาก และ หาปีกกาจบ code ง่ายด้วย
props: {
value: {
type: String,
required: true
}
},
computed: {
formattedValue: function () {
// ...
},
inputClasses: function () {
// ...
}
}
// Good
props: {
value: {
type: String,
required: true
}
},computed: {
formattedValue: function () {
// ...
},
inputClasses: function () {
// ...
}
}
Priority D Rules: Use with Caution (Potentially Dangerous Patterns)
v-if
/v-if-else
/v-else
without key
ในการใช้ v-if ควรระวังในกรณีที่คุณไม่ได้กำหนด Key ให้มันปกติเราจะเขียน v-if และ v-if-else ให้ HTML ติดกัน แต่บางครั้งอาจจะลืม หรือ ใส่ผิดที่ก็ระเบิดได้
<div v-if="error">
Error: {{ error }}
</div>
<div v-else>
{{ results }}
</div>
// Good
<div v-if="error" key="search-status">
Error: {{ error }}
</div>
<div v-else key="search-results">
{{ results }}
</div>
Parent-child communication
ถ้า parent และ child จะคุยกัน หรือ ส่ง event หากันควรใช้ emit เท่านั้นครับอย่าพยายามใช้ this.$parent
เพื่อ access function parent เพราะเมื่อไหร่ที่เราย้าย child หรือ reuse component อาจจะหา function ไม่เจอแล้ว error ไปเลยก็ได้ครับ
จบแล้วเล่นเอาเหนื่อยเหมือนกันแต่ผมก็พยายามเขียนเกือบทั้งหมดเพราะเท่าที่อ่านดูสำคัญทุกอย่างถ้าใครอยากอ่านแบบเต็มๆก็ตามไปอ่านได้ที่ Reference ด้านล่างเลยครับ และ ใครที่อ่านจนจบถือว่า อึดทนแข็งแรงมาก ขอให้เขียนโปรแกรมโหดขึ้นทุกวันทุกคืน และ ขอขอบคุณทุกท่านที่ติดตามอ่านบทความนะครับ ถ้าผิดพลาดตรงไหนบอกผมได้เลยครับผมจะรีบแก้ไข