Trong khi Vue.js là framework được xây dựng từ javascript được sử dụng cho client-side app, Nuxt.js là framework được phát triển dựa trên Vue.js. Có thể hình dung Nuxt là phiên bản được thiết lập sẵn để dễ dàng phát triển universal app hoặc spa. Do vậy core của Nuxt.js bao gồm Vue.js, Vue Router, VueX, Vue SSR và Vue Meta.

Nuxt Schema/Lifecycle

Như đã nói ở trên, Nuxt.js là framework được xây dựng từ Vue.js nên vòng dời của Nuxt sẽ bao gồm vòng đời của Vue và vòng đời của Nuxt trước khi chương trình Vue được bắt đầu. Trong bài viết này chúng ta sẽ tập trung tìm hiểu vòng đời của Nuxt.js trước khi chạy chương trình Vue.

Lược đồ bên dưới hiển thị những phương thức được gọi bởi Nuxt.js khi được gọi bởi server hoặc khi người dùng điều hướng thông qua nuxt-link.

Bởi vì đây là vòng đời được khởi chạy trước khi chạy chương trình vue nên bạn có thể hiểu nó chạy trước cả khi khai báo page component nên this sẽ không phải của vue instance nhé. Bù lại các phương thức này đều nhận biến context là tham số nên rất đa dụng đó 😘😘

Chúng ta sẽ đi sâu vào từng hook ở lược đồ trên theo thứ từ từ trên xuống nha 😆😆

nuxtServerInit

Phương thức này là 1 action của VueX, nếu nó được khai báo trong store thì Nuxt.js sẽ gọi action này mỗi khi Nuxt.js bắt đầu lifecycle mới. Do đó phương thức này rất hữu ích khi chúng ta muốn nhận và lưu trữ dữ liệu dùng chung cho tất cả pages từ server vào store của client.

// store/index.js
actions: {
  nuxtServerInit({ commit }, { req }) {
    if (req.session.user) {
      commit('setUser', req.session.user);
      return axios.get(`/users/${req.session.user}`).then((response) =>{
        commit('currentUserData', response.data);
      });
    }
  },
}

Nếu action này trả về Promise thì Nuxt.js sẽ chờ promise đó được giải quyết xong mới tiếp tục chạy phương thức tiếp theo.

Lưu ý là nuxtServerInit chỉ được gọi ở main store nên nếu bạn sử dụng Modules mode cho Vuex thì bạn cần gộp những actions lại.

// store/index.js
actions: {
  nuxtServerInit ({ dispatch }, context) {
    return Promise.all([ // 👈 mình dùng Promise.all() để kết hợp tụi nó
      dispatch('user/nuxtServerInit', context),
      dispatch('news/nuxtServerInit', context),
    ]);
  },
}

middleware

Như tên gọi ha. middleware cho phép bạn khai báo và thực thi các hàm trước khi khởi tạo page 😘😘

Không giống như nuxtServerInit (chạy cho tất cả các pages), bạn có thể sử dụng global middleware trong nuxt.config.js cho tất cả pages, hoặc sử dụng cho page cụ thể bằng phương thức middleware trong layout/page đó.

Đặc điểm của middleware là chúng ta có thể tái sử dụng được code vì vậy đây là nơi tốt nhất để kiểm tra đăng nhập hoặc lấy dữ liệu chung cho các pages cần sử dụng.

// middleware/check-authenticate.js
export default function({ store, redirect }) {
  // Chưa đăng nhập thì đuổi về trang login 😛😛
  if (!store.state.authenticated) {
    return redirect('/login');
  }
}
  
// middleware/common-fetch.js
import axios from 'axios'

export default function ({ route }) { // 👈 Tham số đầu tiên là context nè
  return axios.post('http://api-cua-tui.com', {
    url: route.fullPath
  });
}

Vì có thể đặt middleware ở nhiều vị trí nên Nuxt.js quy ước thứ tự ưu tiên thực thi middleware như sau:

  1. nuxt.config.js
  2. Layouts có middleware
  3. Pages có middleware

validate()

validate() được gọi trước mỗi lần chuyển đến route mới. Đây là phương thức phù hợp nhất để kiểm tra tham số và xác nhận điều hướng.

Theo mặc định, Nuxt.js hiển thị trang 404 nếu trả về false và trang 500 nếu trả về Error hoặc xuất hiện lỗi 🙄🙄

validate({ params, query, store }) {
  return true; // Tạo page component
  return false; // Dừng render và hiển thị 404 cho zui
  throw new Error('Lỗi rồi ông êi!'); // Cũng dừng render nhưng hiển thị 500
}

fetch()

Phương thức fetch được sử dụng để lấy và lưu trữ dữ liệu vào store trước khi hiển thị page, nhưng khác với nuxtServerInit là thay vì áp dụng cho toàn bộ pages, phương thức này chỉ áp dụng cho 1 page cụ thể.

Nếu fetch() trả về Promise thì Nuxt.js sẽ đợi promise đó được giải quyết trước khi hiển thị page.

Bổ sung: Từ phiên bản 2.12 trở lên, fetch có thể được gọi ở bất kỳ Vue component nào đó. Nhưng nó cũng bị khuyến khích không dùng đối với page component nữa rồi, thay vào đó bạn có thể sử dụng middleware(context) cho page.

// pages/products/_id.vue
export default {
  fetch(({ store, params }) {
    if (typeof (store.state.products.byId[params.id]) === 'undefined') {
      return store.dispatch('products/loadProduct', { id: params.id });
    };
  },
}

asyncData()

asyncData() có rất nhiều sự tương đồng với fetch() ngoại trừ kết quả được return từ asyncData sẽ hợp nhất với data của page component vì vậy phương thức này rất hữu ích nếu bạn không muốn lưu dữ liệu vào store.

export default {
  asyncData({ params, error }) {
    return axios.get(`https://api-cua-tui/posts/${params.id}`)
      .then((res) => {
        // Lưu res.data.title vào data { title } của page component
        return { title: res.data.title };
      })
      .catch((e) => {
        // Chuyển về trang 404
        error({ statusCode: 404, message: 'Post not found' }); 
      });
  },
}

Tổng kết

Vậy là chúng ta đã cùng tìm hiểu về vòng đời của Nuxt.js. Hy vọng sau bài này các bạn có thêm nhiều lựa chọn để giải quyết vấn đề trong ứng dụng Nuxt của mình 😘😘

Nguồn tham khảo