Vuejs: Refactoring code thật sạch sẽ

Vuejs là một framework tuyệt vời, nó cho chúng ta khả năng tạo ra những trang web phức tạp nhưng cách làm thì thật đơn giản.

Tuy đã sử dựng Vue khá nhiều nhưng mình vẫn chưa thật sự tận dụng được sự những sức mạnh này của Vue, đôi khi code Vue của mình quá dài, quá phức tạp khiến cho chính mình khi lôi ra đọc lại cũng không thể ngửi được, có những components dài đến 300 dòng code @@

Sau một thời gian nhẫn nhịn cuối cùng cũng chịu tìm hiểu cách refactor lại cho code dễ đọc, bỏ những phần thừa thãi để tăng thêm hiệu năng cho project, dưới đây mà một số cách mà mình sưu tầm được và tự mò ra trong quá trình làm việc với Vue.

Trước hết hãy xem qua ví dụ thực tế mà chúng ta sẽ refactor lại:

<template>
    <div class="article-preview">
        <rwv-article-meta
            :article="article"
            :isPreview="true"
        >
        </rwv-article-meta>
        <router-link
            :to="{name: 'article', params:{'slug': article.slug}}"
            class="preview-link">
            <h1>{{article.title}}</h1>
            <p>{{article.description}}</p>
            <span>Read more...</span>
            <ul class="tag-list">
                <li class="tag-default tag-pill tag-outline"
                v-for="(tag, index) of article.tagList"
                :key="tag + index">
                    {{ tag }}
                </li>
            </ul>
        </router-link>
    </div>
</template>

<script>
import RwvArticleMeta from "@/components/AricleMeta";

export default {
    name: "RwvArticlePreview",
    props: {
        article: { type: Object, required: true }
    },
    components: {
        RwvArticleMeta
    }
}
</script>

Components này có nhiệm vụ render ra một list các bài báo dưới dạng preview để người dùng click vào đọc, rất đơn giản vậy thôi. Nhìn vào có thể thấy components này còn khá là rối rắm, đấy là nó còn đơn giản, chứ phải mấy components mà có thêm form với cả các tác vụ xử lý nữa lên tầm 300 dòng code thì các bạn nghĩ xem sẽ thối đến mức nào 😄

Bây giờ chúng ta sẽ đi refactor lại nó, có thể cách làm của mình không phải tốt nhất nên mình mong sẽ nhận được các ý kiến đóng góp trong phần comment (bow)

1. Sử dụng Import Components hiệu quả

Ở ngay dòng Import đầu tiên các bạn có thể thấy mình đang dùng alias của webpack để thực hiện import component RwvArticleMeta, ký tự @ ở đây thay thế cho đường dẫn root của project đến component mà mình cần. Rất ngắn gọn nhưng chưa thật sự hiệu quả, vì khi bạn cần chuyển từ webpack sang một module bundler khác thì có thể, module mới không hỗ trợ đường dẫn này.

Thực tế mình đã gặp trường hợp này khi thực hiện một project Vue nhỏ, ban đầu mình sử dụng Nuxt.js để code, mọi chuyện ok, nhưng đến khi nộp cho leader, anh leader lại muốn chuyển hết nó sang Vue template đơn giản vì project không cần SSR. Mình đem nó về và cắt hết các components sang chỗ mới, nhưng khi render thì lỗi tè le vì Vue template mới không sử dụng webpack nên không hỗ trợ express path @.

Và thế là mình đã phải đổi hết sang như này:

// từ
import RwvArticleMeta from "@/components/ArticleMeta";

//thành
import RwvArticleMeta from "../../components/ArticleMeta";

2. Sắp xếp các thuộc tính của Components

Cái này rất nhỏ thôi, đó là theo như document của Vue thì ta sẽ sắp xếp khai báo các props sau components, nhưng theo mình thì nên đặt props ở sau, vì thường chúng ta sẽ sử dụng các props trong methods, watch hoặc computed nên đặt vậy cho dễ xem, đỡ phải kéo lên nhiều thôi chứ cũng không có gì :v

<script>
export default {
    name: "RwvArticlePreview",
    props: {
        article: { type: Object, required: true }
    },
    components: {
        RwvArticleMeta
    }
}
</script>

// nên là
<script>
export default {
    name: "RwvArticlePreview",
    
    components: {
        RwvArticleMeta
    },
    
    props: {
        article: { type: Object, required: true }
    }
    
    data(){},
    
    watch: {},
    
    methods: {},
    //...
}
</script>

3. PascalCase Components

Lại thêm một điều nhỏ nhặt đơn giản nữa mà mình đã làm rất nhiều đó là thay vì viết component dưới dạng HTML thì chuyển qua PascalCased, điều nhỏ nhặt này có thể tạo ra khác biệt lớn, giảm thời gian đọc code của các dev xuống.

Khi nhìn vào một component với rất nhiều các component được import, các dev sẽ có thể lướt qua ngay lập tức component nào được sử dụng ở đâu khi sử dụng cú pháp PascalCase, nhất là khi phải đọc code của một bạn khác. Mình dám cá là nó sướng mắt hơn nhiều so với custom HTML tag truyền thống

<rwv-article-meta
    :article="article"
    :isPreview="true">
</rwv-article-meta>

// hãy làm thế này
<RwvArticleMeta
    :article="article"
    :isPreview="true">
</RwvArticleMeta>

4. Self-close elements

Lại một điều nhỏ nhặt nữa có thể giúp code của bạn bớt dài dòng đi rất nhiều. Hãy tích cực sử dụng chúng trừ khi bạn thật sự cần đặt một dòng plain text ở trong element đó

<RwvArticleMeta
    :article="article"
    :isPreview="true">
</RwvArticleMeta>
                
// nên là
<RwvArticleMeta
    :article="article"
    :isPreview="true"
/>

5. Rút gọn các Props

Đôi khi chúng ta cần truyền các props là các giá trị Boolean, những lúc như thế không cần truyền cho chúng giá trị true hay false, chỉ cần đặt nó vào trong component là được rồi, nếu chúng được đặt ở đó, Vue sẽ mặc định ghi nhận giá trị true, ngược lại là false

<RwvArticleMeta
    :article="article"
    :isPreview="true"
/>

// nên là
<RwvArticleMeta
    :article="article"
    :isPreview 
/>

6. Truyền tham số ngắn gọn

Đôi khi bạn sẽ muốn truyền sang phía component con một vài tham số là kiểu Object, hoặc kiểu mảng và dĩ nhiên là chúng khá là dài, lúc này đừng quên bạn có một công cụ tuyệt vời: Computed.

Ở ví dụ dưới đây bạn có thể thấy, component router-link được truyền vào một tham số to, hiện nó đang là một object khá dài, chúng ta sẽ đưa toàn bộ phần giá trị này vào một hàm ComputedarticleLink(). Lúc này trên template của bạn, articleLink sẽ được coi như một value để sử dụng.

<router-link
    :to="{name: 'article', params:{'slug': article.slug}}"
    class="preview-link">
    //...
</router-link>

// nên là
<router-link
    :to="articleLink"
    class="preview-link">
    //...
</router-link>

<script>
import RwvArticleMeta from "@/components/AricleMeta";

export default {
    props: {
        article: { type: Object, required: true }
    },

    computed: {
        articleLink() {
            return {
                name: 'article',
                params: {
                    slug: this.article.slug
                }
            }
        }
    }
}
</script>

7. Đưa tất cả element vào trong HTML tag

Trong đa số các trường hợp, bạn nên đặt tất cả các element của mình vào trong một tag HTML, kể cả nó là plain text. Có thể là một thẻ span hoặc div nào đó cũng được, điều này để phục vụ cho công việc sau này, biết đâu trong tương lai bạn sẽ cần sử dụng if else trên element đó, hoặc đơn giản là bạn cần css cho dòng plain text đó.

<li class="tag-default tag-pill tag-outline"
    v-for="(tag, index) of article.tagList"
    :key="tag + index">
    {{ tag }}
</li>

// nên là
<li class="tag-default tag-pill tag-outline"
    v-for="(tag, index) of article.tagList"
    :key="tag + index">
    <span>{{ tag }}</span>
</li>

8. Dùng v-text thay cho cú pháp mustache

Nếu bạn đọc trong document của Vue bạn sẽ không thấy các refactor này đâu vì nó là một trick đơn giản giúp bạn tránh được một số lỗi không đáng có.

Cụ thể là khi bạn gửi một HTTP request để lấy data và đổ vào template như ví dụ 7, nếu dữ liệu trả về không có giá trị tag thì rất có thể component sẽ báo lỗi undefined và lỗi gì đó trông kỳ cục, ảnh hưởng xấu đến trải nghiệm của người dùng.

Để xử lý vấn đề này, thay vì trực tiếp đổ dữ liệu như ở trên, hãy đưa nó vào directive v-text, vấn đề này có thể được giải quyết. Hơn thế nữa, với trick này, bạn sẽ có thể sử dụng kèm thêm trick số 4, self-close luôn cả HTML tag này vào, code trông sẽ gọn đi khá là nhiều.

<router-link
    :to="articleLink"
    class="preview-link">
    <h1>{{article.title}}</h1>
    <p>{{article.description}}</p>
    <span>Read more...</span>
    <ul class="tag-list">
        <li class="tag-default tag-pill tag-outline"
        v-for="(tag, index) of article.tagList"
        :key="tag + index">
            {{ tag }}
        </li>
    </ul>
</router-link>

//nên là
<router-link
    :to="articleLink"
    class="preview-link">
    <h1 v-text="article.title" />
    <p v-text="article.description" />
    <span>Read more...</span>
    <ul class="tag-list">
        <li class="tag-default tag-pill tag-outline"
        v-for="(tag, index) of article.tagList"
        :key="tag + index">
            <span v-text="tag" />
        </li>
    </ul>
</router-link>

9. Extract Components

Khi thấy có một số thành phần trong component lặp đi lặp lại, bạn nên nghĩ ngay đến việc tách chúng sang làm một component con để tái sử dụng, tránh việc lặp lại code không cần thiết và nếu có thay đổi gì sẽ chỉ cần thay đổi ở một chỗ.

Chính ở phần giới thiệu đầu tiên mình đã mắc phải lỗi này, do quá tham code mà đã quên mất việc tách component ra, khiến cho code bị đội vốn lên đến 300 dòng và rất nhiều phần bị lặp lại, bây giờ để bảo trì lại thật sự là mất quá nhiều thời gian, thà đập quách đi xây lại cho nhanh.

Ở ví dụ trên, chúng ta có thể thấy mình có một tag li được cho vào vòng lặp, vậy nên không có lý do gì mà không tách nó làm một component riêng, được tái sử dụng nhiều lần, đừng vì lười tạo ra một file mới mà cứ để nguyên vậy như mình, sau này mới thấy rắc rối mà không sửa được ✌️

<router-link
    :to="articleLink"
    class="preview-link">
    //...
    <ul class="tag-list">
        <li class="tag-default tag-pill tag-outline"
        v-for="(tag, index) of article.tagList"
        :key="tag + index">
            <span v-text="tag" />
        </li>
    </ul>
</router-link>

// nên là
<router-link
    :to="articleLink"
    class="preview-link">
    //...
    <TagList :tags="article.tagList" />
</router-link>

<script>
import TagList from './TagList.vue';
export default {
    components: {
        //...,
        TagList
    }
}
</script>

TagList.vue

<template>
    <ul class="tag-list">
        <li class="tag-default tag-pill tag-outline"
            v-for="(tag, index) of tags"
            :key="index"
        >
            <span v-text="tag" />
        </li>
    </ul>
</template>

<script>
export default {
    name: "TagList",
    props: {
        tags: Array
    }
}
</script>

Lời kết

Đó là một số cách cơ bản để có thể thực hiện refactoring code mà mình biết trong quá trình làm việc với Vue. Tất nhiên là còn nhiều thủ thuật nữa từ cơ bản đến nâng cao, qua quá trình làm việc dần dà bạn sẽ tự học được cho mình thêm các cách để code clean hơn, tăng hiệu quả công việc.

Với một số cách refactor code nâng cao hơn, bạn có thể sẽ muốn đọc lại bài viết này của mình: Xây dựng Vue Components một cách xịn xò hơn

Cám ơn đã theo dõi bài viết!