แนวทางการเขียน 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

สวัสดีครับผม อิทธิพัทธ์ (เป้) ชอบหาเทคนิคต่างๆที่ทำให้ชีวิต Programmer ง่ายขึ้น ทั้ง Automate, Library ชอบทำ Blog และ Video ถ้ามีเวลานะ!

ขอบคุณทุกคนที่ติดตาม และอ่านบทความของผมครับ ผมหวังว่าความรู้ที่เขียนขึ้นในเว็บไซต์นี้จะช่วยทุกท่านได้ไม่มากก็น้อย 

Scroll to Top