+1

Vue Instance Lifecycle - Vòng đời của Vue Instance

Trong series các kĩ thuật trong vue.js này sẽ đề cập đến các yếu tố quan trọng mà một lập trình viên vue.js cần nắm rõ. Sẽ là hành trang giúp newbie có thể không bị 'choáng ngợp' khi tham gia một dự án vue.js lần đầu tiên. Trong các bài viết này, sẽ trình bày theo cả cách viết của Options API (Vue.js 2.x, 3.x) và Composition API (Vue.js 3.x) về việc sử dụng các lifecycle hooks.

1. Đặt vấn đề

Khi phát triển một ứng dụng web, việc quản lý và theo dõi vòng đời (lifecycle) của một đối tượng (object), một thể hiện (instance) từ khi được tạo ra và đến khi bị hủy là rất quan trọng. Do vậy, việc quản lý đúng cách các trạng thái và hành vi của các object, instance là việc cần thiết.

Vue.js, một framework javascript để phát triển giao diện người dùng, cung cấp một cơ chế mạnh mẽ để thực hiện các thao tác nhằm can thiệp được vào vòng đời của các instance thông qua các hooks vòng đời (lifecycle hooks).

Trước khi đi tìm hiểu các hooks và tác dụng của chúng trong một dự án vue.js, chúng ta cần nắm được các khái niệm cơ bản liên quan vòng đời của một instance trong vue.js, cũng như vai trò của các lifecycle hooks trong việc quản lý vòng đời của instance trong Vue.

2. Các khái niệm

  • Lifecycle: mỗi component instance đều trải qua một chuỗi các bước như tạo, cập nhật, hủy bỏ,... Một quá trình đầy đủ từ lúc tạo ra đến lúc hủy bỏ là một lifecycle của instance đó.
  • Hook: Hook là một khái niệm trong lập trình giúp bạn can thiệp vào một phần của chương trình mà không thay đổi trực tiếp mã nguồn gốc. Khi "hook" vào một đoạn mã, bạn có thể thêm vào hoặc thay đổi hành vi của đoạn mã đó mà không cần sửa mã gốc. Đây là cách rất hữu ích để mở rộng hoặc tùy chỉnh một hệ thống mà không cần thay đổi cấu trúc ban đầu của nó.
  • Lifecycle hooks: Lifecycle Hook là một dạng hook đặc biệt, giúp can thiệp vào các giai đoạn quan trọng trong vòng đời của component trong các framework như Vue.js.

3. Lifecycle hooks trong Vue.js

Dưới đây là sơ đồ luồng của một vòng đời của một instance. image.png

beforeCreate hook

Hook này được gọi ngay lập tức khi instance được khởi tạo và các thuộc tính (props) được giải quyết. Tức là Vue sẽ đảm bảo rằng các giá trị truyền qua props được nhận và chuẩn bị sẵn sàng để sử dụng trong instance. Trong hook này, không thể truy cập vào các biến và hàm trong data() và methods. Ví dụ: Để sử dụng beforeCreate hook, cần viết theo cách Options API, do cách viết Composition API ( được giới thiệu trong vue.js 3.x) không còn hỗ trợ.

Options API:

<script>
export default {
  data() {
    return {
      message: 'Hello world',
    }
  },
  beforeCreate() {
    console.log('Before create')
    this.message = 'Welcome my web'
    // this.message vẫn là 'Hello world' khi được in ra ở template
  }
}
</script>

Note: Tất cả các hook ngoại trừ beforeCreate thì đều hướng đến instance hiện tại, nghĩa là các hook không phải beforeCreate đều có thể truy cập vào các biến, hàm đã khai báo trong component.

created hook

Hook này được gọi sau khi các thành phần sau đây đã sẵn sàng: các biến reactive, computed properties, hàm và các watcher (các khái niệm này sẽ được giải thích ở bài sau). Tương tự với beforeCreate, created hook chỉ được hỗ trợ trong Options API. Tại đây, có thể gọi API để lấy dữ liệu, khởi tạo giá trị mặc định, kết nối websocket, v.v..

Ví dụ:

<script>
import Book from './Book.vue'
export default {
  components: {Book},
  data() {
    return {
      books: []
    }
  },
  async created() {
    console.log('Created')
    const response = await fetch('http://localhost:3000/books')
    this.books = await response.json()
  }
}
</script>

beforeMount hook

Sau khi Vue tạo thành công một instance của component và thiết lập thành công data, method và các computed. Vue sẽ render nội dung của component, tạo ra HTML tương ứng từ template của component. Sau đó sẽ gắn nội dung HTML vào DOM. Hook beforeMount được gọi trước khi component được gắn (mount) vào trong DOM. Do vậy, thời điểm hook này được gọi thì component vẫn chưa hiển thị lên giao diện. Lúc này, có thể thực hiện một số công việc như khởi tạo dữ liệu, khởi tạo thư viện bên ngoài, gọi api, v.v..

Với Options API, có thể thấy created

Ví dụ:

Options API:

<script>
import Book from './Book.vue'
export default {
  components: {Book},
  data() {
    return {
      books: []
    }
  },
  methods: {
    async fetchBooks() {
      const response = await fetch('http://localhost:3000/books');
      this.books = await response.json();
    }
  },
  beforeMount() {
    this.fetchBooks()
  }
}
</script>

Composition API:

<script>
import { ref, onBeforeMount } from 'vue';
import Book from './Book.vue'

export default {
  components: {Book},
  setup() {
    const books = ref([]);
    const fetchBooks = async () => {
      const response = await fetch('http://localhost:3000/books');
      books.value = await response.json();
    }
    onBeforeMount(() => {
      fetchBooks();
    });
    return {books}
  },
}
</script>

Với Composition API, có cách viết khác ngắn gọn hơn đó là:

<script setup>
import { ref, onBeforeMount } from 'vue';
import Book from './Book.vue'
const books = ref([]);
const fetchBooks = async () => {
  const response = await fetch('http://localhost:3000/books');
  books.value = await response.json();
}
onBeforeMount(() => {
  fetchBooks();
});
</script>

Từ ví dụ này có thể thấy có chút sự khác nhau khi dùng lifecycle hook giữa Options API và Composition API.

Trong Options API thì các hook là một phần của Vue instance. Các hook này được Vue tự động nhận diện khi bạn khai báo chúng trong đối tượng component mà không cần phải import.

Trong Composition API, onBeforeMount là một hook mà bạn cần phải import từ vue. Điều này là bởi vì trong Composition API, các hook như onBeforeUnmount, onMounted, onUpdated, v.v., là các hàm riêng biệt mà được gọi trong hàm setup(). Vì Vue không tự động nhận diện lifecycle hook trong Composition API như trong Options API, cần phải import các lifecycle hooks trước khi sử dụng chúng trong setup().

Ngoài ra sẽ thấy một yếu tố nữa là với Composition API có 2 cách viết. Chúng khá giống nhau, tuy nhiên có một chút khác biệt.

setup() trong export default: Điều này phù hợp khi cần sử dụng các tính năng từ Options API, hoặc cần thêm các cấu hình như data, methods, computed, v.v

script setup: Cú pháp ngắn gọn, khi chỉ làm việc với Composition API và không cần khai báo export default hoặc setup()

Từ các hook sau sẽ chỉ viết theo cách script setup

mounted hook

Hook này được gọi sau khi nội dung HTML được render trước đó đã mount vào DOM. Trong mounted này, cũng có thể thực hiện các công việc như với beforeMounted. Tuy nhiên có một chút sự khác biệt ở đây, khi component được gắn vào DOM thì trong mounted, bạn có thể thực hiện các thao tác với phần tử trong DOM.

Ví dụ:

<script setup>
import { onBeforeMount, onMounted } from 'vue';
onBeforeMount(() => {
  console.log(document.querySelector('.book-list')); // null
});
onMounted(() => {
  console.log(document.querySelector('.book-list')); // object
});
</script>

beforeUpdate hook

Sau khi nội dung HTML của component được mount, hook này sẽ được gọi trước khi component được render lại sau khi có sự thay đổi trong dữ liệu reactive.

Ví dụ:

<template>
  <div>
    <p>{{ message }}</p>
    <button @click="changeMessage">Change Message</button>
  </div>
</template>

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

const message = ref('Hello')

const changeMessage = () => {
  message.value = 'Hello, Vue!'
}

onBeforeUpdate(() => {
  console.log('Message is about to be updated');
})
</script>

Khi bấm nút Change Message thì giá trị của message thay đổi, dẫn đến việc component được render lại để cập nhật và hiển thị giá trị mới. Ngay trước khi component được render lại thì hook beforeUpdate sẽ được gọi.

updated hook

Tương tự beforeUpdate, hook updated được gọi sau khi component đã hoàn thành việc render lại.

Ví dụ:

<script setup>
import { ref, onUpdated } from 'vue'
onUpdated(() => {
  console.log('Message is about to be updated');
})
</script>

Note: updated và beforeUpdate chỉ được gọi khi component được render lại do có sự thay đổi của dữ liệu reactive, do vậy, sự thay đổi dữ liệu reactive trong các hook như created, beforeMount, unmounted, beforeUnmounted sẽ không kích hoạt hook liên quan update. Lý do là vì dù có sự thay đổi của dữ liệu reactive nhưng không xảy ra việc render lại component.

beforeUnmount hook

Hook này được gọi ngay trước khi component bị unmount. Unmount là việc xóa component khỏi DOM. Trong hook liên quan unmount được dùng để dọn dẹp bộ nhớ, tránh rò rỉ bộ nhớ. Với ví dụ dưới đây có thể thấy việc dọn dẹp bộ nhớ là vô cùng quan trọng.

Ví dụ:

// App.vue
<template>
  <button @click="showTime = !showTime">Show time</button>
  <count-time v-if="showTime"/>
</template>
<script>
import CountTime from './components/CountTime.vue'
export default {
  components: {CountTime},
  data() {
    return {
      showTime: true,
    }
  },
}
</script>
// CountTime.vue
<template>
    <p>Current Time: {{ currentTime }}</p>
</template>

<script setup>
import { ref, onBeforeUnmount, onUnmounted } from 'vue';
const currentTime = ref(new Date().toLocaleTimeString());
const updateTime = () => {
  currentTime.value = new Date().toLocaleTimeString();
  console.log('Update time');
};
const timer = setInterval(updateTime, 1000);
</script>

Với ví dụ này, có thể thấy, ngay cả khi component bị unmount, thì trong console vẫn in ra 'Update time'. Do sau khi đăng ký các sự kiện như 'setTimeout', 'setInterval' khi mà không được hủy thì khi component bị hủy thì chúng vẫn sẽ chạy ngầm. Khi này, các hook liên quan unmount sẽ phát huy tác dụng.

onBeforeUnmount(() => {
  console.log('Component is about to be unmounted. Cleaning up...');
  clearInterval(timer);
});

unmounted hook

Hook này được gọi sau khi component đã bị unmount. Trong này có thể làm các công việc tương tự như beforeUnmount, tuy nhiên có một chút sự khác biệt. Trong beforeUnmount, bạn có thể thực hiện thao tác với DOM, còn trong unmounted thì không.

Ví dụ:

onBeforeUnmount(() => {
  console.log(document.querySelector('.book-list')); //object
});

onUnmounted(() => {
  console.log(document.querySelector('.book-list')); // null
});

Note: Trong cả beforeUnmount và mounted, vẫn có thể truy cập các biến và hàm mà được được định nghĩa trong setup() hoặc data() và methods, v.v...

4. Tổng kết

Qua phần tìm hiểu trên, bài viết đã trình bày một số khái niệm, tác dụng, cú pháp của các lifecycle hook cơ bản giúp chúng ta theo dõi và điều khiển quá trình vòng đời của component. Các hook này cho phép chúng ta thực hiện các tác vụ quan trọng ở các thời điểm khác nhau trong vòng đời của component, ví dụ như chuẩn bị dữ liệu, cập nhật DOM, hoặc xử lý khi component bị hủy.

Tham khảo

https://vuejs.org/guide/essentials/lifecycle.html

https://vuejs.org/api/options-lifecycle.html

https://vuejs.org/api/composition-api-lifecycle


All rights reserved

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í