Home » Vuejs » แนวทางการเขียน Vue 2 ให้ชีวิตดี้ดี

เมื่อไม่นานมานี้ 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 ด้านล่างเลยครับ และ ใครที่อ่านจนจบถือว่า อึดทนแข็งแรงมาก ขอให้เขียนโปรแกรมโหดขึ้นทุกวันทุกคืน และ ขอขอบคุณทุกท่านที่ติดตามอ่านบทความนะครับ ถ้าผิดพลาดตรงไหนบอกผมได้เลยครับผมจะรีบแก้ไข

Reference

เกี่ยวกับผู้เขียน

Itthipat Thitsarak

สวัสดีครับผม อิทธิพัทธ์ (เป้) เป็น Freelance Web developer ชอบหาเทคนิคต่างๆที่ทำให้ชีวิต Programmer ง่ายขึ้นโดย Blog นี้จะ สอน Laravel, Vuejs, CSS, HTML 5 และอื่นๆ ที่เกี่ยวกับการทำเว็บไซต์

ขอบคุณทุกคนที่ติดตาม และอ่านบทความของผมครับ หากใครมีคำถามหรืออยากให้ผมเขียนเกี่ยวกับเรื่องอะไรเพิ่มเติม สามารถแสดงความคิดเห็นไว้ที่ใต้บทความ หรือส่งเรื่องเข้ามาที่ Email ได้เลยครับ หัวข้อไหนน่าสนใจ ผมจะหยิบมาเขียนบทความให้ได้อ่านกันเรื่อยๆครับ

Scroll to Top