Bài 11: Cách sử dụng forceUpdate trong VueJS

Chào mừng các bạn quay trở lại với series học VueJS của mình, ở bài trước ta đã tìm hiểu về vòng đời của Vue instance, và bài này sẽ giới thiệu cho các bạn về các re-render lại DOM khi cần thiết, và các lỗi liên quan. Mình tổng hợp dựa vào những gì mình học được trong quá trình phát triển ứng dụng.

Giới thiệu $forceUpdate

forceUpdate được sử dụng khi các bạn cần thiết re-render lại DOM. Bằng cách sử dụng forceUpdate ta có thể bảo Vue re-render lại DOM khi cần thiết, hiển thị dữ liệu một cách chính xác.

Để sử dụng các bạn đơn giản là gọi this.$forceUpdate, Vue sẽ làm mọi thứ còn lại là tìm đến phần dữ liệu bị thay đổi và render lại phần đó.

Cách sử dụng

Chắc có lẽ VueJS là framework đầu tiên mà mình thích đọc nghiền ngẫm docs của nó, và ở trong docs có một câu rất quan trọng, mình xin dùng trình tiếng Anh của bản thân để dịch cho các bạn 😃 : để Vue có thể tự động render lại DOM thì các dữ liệu phụ thuộc phải được khai báo ban đầu là "reactive data" hoặc được thiết lập bằng cách gọi Vue.set()

Mình sẽ hướng dẫn các bạn cách sử dụng thông qua ví dụ dưới đây nhé:

<template>
    <div>
        <div>
            Name: {{ person.name }}
        </div>
        <div>
            Nick name: {{ person.nickname }}
        </div>
        <div>
            <button @click="changeName">Change Name</button>
        </div>
        <div>
            <button @click="changeNickname">Change Nick Name</button>
        </div>
        <div>
            <button @click="changeNicknameProperly">Change NIck name properly</button>
        </div>
    </div>
</template>
<script>
export default {
    data() {
        return {
          person: {
                name: 'Edson'
            }
        }
    },
    methods: {
        changeName() {
            this.person.name = 'Arantes';
        },
        changeNickname() {
            this.person.nickname = Math.random().toString(36).substring(7);
        },
        changeNicknameProperly() {
            Vue.set(this.person, 'nickname', 'Louis');
        }
    }
}
</script>

<style lang="scss" scoped>
</style>

Ở đây các bạn thấy dữ liệu ban đầu mình khai báo person chỉ có name, và 3 hàm:

  • changeName: để thay đổi name
  • changeNickname: để vừa tạo vừa thiết lập giá trị nickname, mình thiết lập một giá trị nickname ngẫu nhiên mỗi lần click dùng hàm random
  • changeNicknameProperly: để mô tả cách sử dụng đúng khi tạo nickname Ở phần template bên trên mình có in ra namenickname cùng với đó là 3 button gọi đến 3 phương thức ta vừa nói ở trên. Sau đó các bạn save file lại, mở trình duyệt, mở Vue-devtool để quan sát nhé.

Đầu tiên khi load trên màn hình chỉ hiện name vì hiện tại nickname chưa có, sau đó click vào button Change Name, ta có thể thấy name đã thay đổi, vì nó là reactive data (được khai báo trực tiếp trong data.

Sau đó ta thử click button Chang nickname, vâng và vẫn chưa thấy nickname xuất hiện trên màn hình. Vào Vue-devtool, click Refresh, đã thấy có nickname rồi mà 😢, ầy gù. Quay lại với câu nói kinh điển mình dịch docs của Vue mà mình nói ở bài. Vì nickname ta không khai báo trong data cũng không dùng Vue.set() để thiết lập nên nó không phải reactive data nên khi nó thay đổi thì DOM sẽ không re-render, các bạn có click đến mỏi tay thì vẫn chỉ có dữ liệu ta nhìn ở Vue-devtool là thay đổi 😃.

"OK, thế fix ra sao vì tôi không thích dùng Vue.set() cũng không muốn đưa nó vào data ngay lúc đầu?". Với trường hợp như thế thì ta chỉ cần gọi this.$forceUpdate để yêu cầu Vue re-render lại DOM, thì sẽ được kết quả mong muốn.

changeNickname() {
    this.person.nickname = Math.random().toString(36).substring(7);
    this.$forceUpdate()
}

Đây là một sai lầm mình rất hay gặp vì nhiều khi quên lúc load data từ backend thì có append thêm một số trường, sau này hiển thị thì không thấy trên màn hình thay đổi 😄. Nhưng ở đây thì nickname vẫn không phải là reactive data nên sau này ở chỗ khác mà các bạn lỡ có thay đổi nó thì Vue vẫn sẽ không render lại và trên browser các bạn cũng sẽ không thấy sự thay đổi đâu nhé. Nên để tốt nhất mình khuyên các bạn nên khai báo nó sẵn ở data hoặc thêm cho nó luôn từ backend trước khi trả về kết quả bên Vue, hoặc làm theo cách bên dưới đây nhé (khuyến khích).

Có một bạn hỏi là có cách nào để tôi không dùng forceUpdate được không thì vẫn có một cách khác cũng khá là đơn giản để DOM re-render mỗi khi nickname thay đổi. Đó là sử dụng Vue.set(). Xem ví dụ sau:

<template>
    <div>
        <div>
            Name: {{ person.name }}
        </div>
        <div>
            Nick name: {{ person.nickname }}
        </div>
        <div>
            <button @click="changeName">Change Name</button>
        </div>
        <div>
            <button @click="changeNickname">Change Nick Name</button>
        </div>
        <div>
            <button @click="changeNicknameProperly">Change NIck name properly</button>
        </div>
    </div>
</template>
<script>
export default {
    data() {
        return {
          person: {
                name: 'Edson'
            }
        }
    },
    methods: {
        changeName() {
            this.person.name = 'Arantes';
        },
        changeNickname() {
            this.person.nickname = Math.random().toString(36).substring(7);
        },
        changeNicknameProperly() {
            Vue.set(this.person, 'nickname', 'Louis');
        }
    }
}
</script>

<style lang="scss" scoped>
</style>

Giải pháp là lần đầu tiên ta click vào button Change NIck name properly để khởi tạo nickname, từ bây giờ nó đã là reactive data và khi ta click lại vào button Change Nick Name thì giờ đây không cần forceUpdate DOM đã re-render, và dữ liệu đã thay đổi trên màn hình.

Kết luận

Qua bài này mong rằng các bạn đã hiểu được cách $forceUpdate hoạt động, qua đó sử dụng một cách đúng đắn tránh được các trường hợp không hiểu vì sao hiển thị dữ liệu không đúng, hoặc những trường hợp thôi cứ thêm vào forceUpdate cho nó chắc 😄.

Hôm nay viết nhiều buồn ngủ quá rồi. Hẹn gặp lại các bạn vào bài tới với cách binding class và style cho các thẻ HTML. Có gì thắc mắc các bạn comment bên dưới nhé ^^!