+31

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

Mình đã cập nhật lại tất cả các bài với các thay đổi ở hiện tại ở năm 2024: Vue 3, Vite, Laravel 11x,...

Cập nhật gần nhất: 08/06/2024

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.

forceUpdate là gì? 🤔

forceUpdate là một phương pháp mà bạn có thể sử dụng để buộc một component Vue cập nhật lại mà không cần phải thay đổi dữ liệu hoặc trạng thái của nó. Điều này có thể hữu ích trong một số tình huống, nhưng cần được sử dụng cẩn thận.

Cách sử dụng forceUpdate

Trong Vue 3, bạn có thể sử dụng forceUpdate từ API toàn cục (global) của Vue. Dưới đây là một ví dụ:

<template>
  <div>
    <p>{{ message }} - {{ count }}</p>
    <button @click="updateMessage">Update Message</button>
    <button @click="updateCount">Update Count</button>
    <button @click="forceUpdateComponent">Force Update</button>
  </div>
</template>

<script setup>
import { ref, getCurrentInstance } from 'vue';

const message = ref('Hello, World!');
const { proxy } = getCurrentInstance();
let count = 1

function updateMessage() {
  message.value = 'Hello, Vue 3!';
}

function updateCount() {
  count++
}

function forceUpdateComponent() {
  proxy.$forceUpdate();
}
</script>

Khi chạy lên ta có UI như sau:

Screenshot 2024-06-08 at 11.53.49 AM.png

Khi bấm Update Message thì ta thấy ngay lập tức message đã thay đổi trên màn hình. Nhưng khi bấm Update Count thì không có gì xảy ra cả.

Lí do là bởi vì ta đang khai báo count chỉ là 1 biến JS thường, nên với Vue nó không phải là reactive state, tức là Vue sẽ không theo dõi khi giá trị của count thay đổi.

Tiếp theo nếu ta bấm Force Update thì sẽ thấy là giá trị của count trên màn hình sẽ được cập nhật.

Giờ nếu ta F5 lại trình duyệt -> Update Count -> Update Message thì cũng thấy giá trị của count thay đổi trên UI, lí do là vì khi ta Update Message thì Vue sẽ tiến hành re-render bởi vì messagereactive state, và count cũng được "ăn hôi" và thay đổi theo 😂😂

Ở Vue 2 cách dùng là gọi: this.$forceUpdate() ở bất kì đâu trong component

Khi Nào Nên Dùng forceUpdate? ⏰

forceUpdate nên được sử dụng trong các tình huống sau:

  • Khi bạn cần cập nhật giao diện mà không có thay đổi dữ liệu rõ ràng.
  • Khi bạn tích hợp với các thư viện bên thứ ba mà không trigger reactivity của Vue.
  • Khi bạn thực hiện các thao tác DOM phức tạp và cần đảm bảo giao diện luôn cập nhật.

Tại Sao Nên Hạn Chế Dùng forceUpdate? ⚠️

Mặc dù forceUpdate có thể hữu ích trong một số tình huống, nhưng nó cũng có những hạn chế và có thể gây ra các vấn đề sau:

  • Hiệu suất: forceUpdate lại toàn bộ component có thể làm giảm hiệu suất, đặc biệt là khi component phức tạp.
  • Khó bảo trì: Sử dụng forceUpdate có thể làm cho code của bạn khó hiểu và khó bảo trì hơn. Điều này đặc biệt đúng khi nhiều developer làm việc trên cùng một dự án.
  • Thiếu minh bạch: forceUpdate bỏ qua hệ thống reactivity của Vue, làm mất đi tính minh bạch và dễ hiểu của ứng dụng.

Kết luận

Bài này siêu ngắn 😂

Hiểu rõ và sử dụng forceUpdate một cách cẩn thận có thể giúp bạn giải quyết một số vấn đề phức tạp trong ứng dụng Vue 3 của mình. Tuy nhiên, hãy luôn nhớ rằng việc lạm dụng forceUpdate có thể dẫn đến các vấn đề về hiệu suất và bảo trì. Luôn ưu tiên sử dụng các phương pháp reactivity tiêu chuẩn của Vue trước khi nghĩ đến forceUpdate.

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 😄.

Nói chung khi code mình hạn chế sử dụng $forceUpdate, với mình đó là giải pháp tệ nhất khi không giải thích được lỗi về data. Như trang chủ Vue đã khuyến cáo:

If you find yourself needing to force an update in Vue, in 99.99% of cases, you’ve made a mistake somewhere.

Tạm dịch:
Nếu bạn thấy mình cần phải sử dụng forceUpdate, thì 99% là bạn đã phạm sai lầm ở đâu đó rồi :-D

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é ^^!


All rights reserved

Bình luận

Đăng nhập để bình luận
Avatar
@duongricky
thg 7 17, 2018 2:31 SA

Vue.set(this.person, 'nickname', 'Louis'); Em hiểu như này đúng k a theo từng giá trị trong vue set từ trái qua phải, giá trị đầu là đối tượng cần trỏ đến, giá trị 2 là thuộc tính của đối tượng, giá trị 3 là value cần set cho thuộc tính ở giá trị 2

Avatar

đúng rồi em 😉

Avatar
@no07
thg 10 30, 2018 6:57 SA

Thêm import Vue from 'vue' trong tag <script>, nếu các bạn sử dụng VueCLI nhé

Avatar
@chauchauonline
thg 11 6, 2018 9:30 SA

bài viết này e thấy 2 ví dụ này giống nhau đấy bác. e gà quá. đọc xong bài viết này không thấy hiểu gì 😦(

Avatar

nếu không hiểu chỗ nào bạn comment cho mình đc biết nhé 😃

Avatar
@backshow
thg 12 16, 2018 10:24 SA

Tại hàm changeNicknameProperly() -> Vue.set( ... ) đổi thành this.$set ( ... ) mới hoạt động.

Xem thêm (4)
Avatar
@backshow
thg 12 16, 2018 4:08 CH

E đang theo hương lập trình laravel + vuejs, nhưng quá nhiều thứ không biết, nếu có thể add skype live:20ceee01d0a0fe39 , hi vọng nhận được nhiều sự hỗ trợ từ anh.

Avatar
@maitrungduc1410
thg 12 17, 2018 1:14 SA

oke bạn nhé mình add rồi, nếu có gì giúp đc thì ae cùng phát triển 😃

Avatar

Em thử lần đầu ấn button "Change Nick Name" sau đó ấn button "Change NIck name properly" rồi quay lại ấn button "Change Nick Name" tại sao nick name vẫn không hiển thị trên web vậy anh?

Avatar

Chào em, khi lần đầu e bấm Change Nick Name khi đó nickname sẽ không reactive, Vue sẽ "không để ý" tới sự thay đổi giá trị của thằng đó, tiếp theo em bấm Change NIck name properly, dù có Vue.set, nhưng thực chất ở đây e chỉ thay đổi giá trị thằng nickname vừa được tạo ra trước đó, mà thằng này không reactive nên Vue sẽ không "quan tâm" và không re-render lại nữa, đó là lí do e không thấy có thay đổi trên màn hình, dù em bấm thế nào đi chăng nữa. Trừ khi em bấm button Change Name khi đó thằng name thay đổi (vì nó được khai báo ở data nên sẽ reactive) ->Vue re-render -> nickname cũng được re-render

Rút ra: chỉ cần từ ban đầu em bấm Change Nick Name là từ đó nickname đã bị coi như không reactive rồi và em sẽ phải dùng forceUpdate từ giờ về sau nếu muốn Vue re-render để thấy thay đổi trên màn hình. Do đó, để nickname là reactive, thì ngay từ ban đầu e phải gọi Vue.set trước, sau đó thì e bấm Change Nick Name thoải mái sẽ thấy nickname liên tục thay đổi. 😉

Avatar

Em cảm ơn anh nhiều ạ 👍

Avatar
@huyla35ca
thg 6 2, 2020 11:04 SA

Khá clean good post

Avatar

🙌🙌

Avatar
@Croud141
thg 11 6, 2020 12:23 CH

Hello anh, lại là em đây, một fan của anh 😆. Nay em đã đi thực tập và đang chiến đấu ở vị trí frontend dev (vuejs). Tuy nhiên nay em có gặp một trường hợp tuy đã tìm ra lời giải nhưng em không thể biết được nguyên nhân và cách làm của mình đã tối ưu hay chưa. Giờ muốn hỏi anh ạ. Cụ thể là em sẽ có 2 component cha con giao tiếp với nhau, nhưng vì một lí do nào đó (mà em không biết 😂) thì dữ liệu nó không reactive theo ý em muốn và nó chỉ hoạt động như em mô tả dưới hình ạ. Capturevue.PNG

Và sau một hồi loay hoay thì cách khắc phục của em đó là bind thằng "key" vào thằng con, mỗi khi thằng cha thay đổi -> random ra một key -> thằng component con render lại. Vậy em muốn hỏi là liệu cách làm vậy có đúng và nguyên nhân của việc data trong thằng con k reactive ạ. Em cảm ơn.

Avatar

Chào e,

Đây là 1 điều ở trong docs của Vue đã nói tới nhé e, gọi là One Way Data flow. Data truyền từ cha xuống con thông qua props, thì nó sẽ chỉ chảy (flow) từ cha xuống, chứ không chảy cả theo chiều ngược lại, điều này tránh việc e mutate trực tiếp props ở con, làm data ở cha thay đổi (mà không qua emit).

Do với ở bài của e, e truyền vào như kia thì chúng chỉ làm giá trị khởi đầu cho con thôi, kể từ sau thì props thay đổi data của con sẽ ko reactive nữa

Thế tại sao this.heySon vẫn reactive??

Ở ngay trong docs kia có note luôn rằng: nếu e truyền array hoặc object từ cha vào con thì vì chúng được truyền xuống bằng địa chỉ (reference), nên cha thay đổi sẽ thấy con reactive ngay, hay nói cách khác, ở đây con có thể thay đổi trực tiếp props dẫn tới data ở phía cha cũng bị thay đổi theo --->> Flow của app bắt đầu bị rối rắm

Do đó a đoán là heySon của e ở cha đang là object hoặc array nên sẽ được truyền xuống con bằng địa chỉ (pass-by-reference) còn heyNeighbor đang là number, string hoặc boolean (truyền xuống bằng giá trị, pass-by-value, sẽ không reactive theo cha)


Giải pháp cho việc này

Việc heySon đang bị truyền vào bằng địa chỉ như kia sẽ làm cho con có thể thay đổi trực tiếp props mà không thông qua emit. Đồng thời a hiểu ý định của e là "truyền props trực tiếp vào data của con, để khi props thay đổi thì data của con cũng reactive theo", nhưng điều này vô hình chung sẽ làm flow của app sẽ rối, không đúng với tư tưởng của Vue là props down, event up (ý là data từ cha xuống con thì qua props, còn con lên cha thì qua emit) và hậu quả là dễ sinh ra lỗi (như lỗi e đang gặp phải vậy 😄 😄).

Cách giải quyết a thường chọn là ở con, e watch props từ cha hoặc dùng computed rồi update vào data của con. Demo:

<script>
// Parent.vue
import Child from './Child'

export default {
  components: {
    Child
  },
  data: () =>  {
    return {
      heySon: {
        name: 'MTD',
        age: 25
      },
      heyNeighbor: 456
    };
  },
  methods: {
      updateSonAge () {
        this.heySon.age = Math.random()
      },
      updateNeighbor () {
        this.heyNeighbor = Math.random()
      }
  },
}
</script>


<script>
// Child.vue
  export default {
    props: ['heySon', 'heyNeighbor'],
    data () {
      return {
        foot: { ...this.heySon }, // tạo giá trị khởi đầu sẽ ko bị reactive theo cha vì đây là object mới
        hands: {
          leftHand: this.heyNeighbor // tạo giá trị khởi đầu, ko bị reactive theo cha vì đây là number
        }
      }
    },
    watch: {
      heySon: {
        deep: true,
        handler () {
          this.foot =  { ...this.heySon } // chỗ này vẫn phải dùng "..." nếu không là `foot` sẽ có cùng địa chỉ với `heySon` và khi con update thì cha sẽ tự update theo
        }
      },
      heyNeighbor () {
        this.hands.leftHand = this.heyNeighbor
      }
    }
    // hoặc dùng computed
    computed: {
      hands () {
        return {
          leftHand: this.heyNeighbor
        }
      }
    }
  }
</script>

Ở trên thì foothands sẽ reactive theo props từ cha, nhưng khi e bind foothands và khi giá trị của chúng thay đổi từ phía con thì data sẽ không tự truyền lại cha, mà e cần phải dùng emit để đồng bộ (nếu e cần thiết điều đó)

Chú ý nữa rằng ở con nếu e thay đổi trực tiếp giá trị của 1 thuộc tính của heySon (nested props) thì sẽ không thấy báo lỗi Avoid mutating a prop directly - ko đc thay đổi props, mặc dù thực tế ta đang thay đổi giá trị thuộc tính của nó. Đây là 1 vấn đề đã đc Evan giải thích ở đây. Mặc dù đây ko phải là lỗi nhưng vẫn ko khuyến khích dùng cách này. Cứ muốn thay đổi data của cha thì luôn dùng emit nhé e.

Avatar
@Croud141
thg 11 8, 2020 4:42 SA

@maitrungduc1410 Dạ em đã hiểu ạ, cảm ơn anh nhiều.

Avatar
+31
Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí