Vue Composition Api là gì? Tại sao chúng ta lại phát cuồng về nó?

3
Dịch vụ dạy kèm gia sư lập trình

Thấm thoắt thoi đưa, Vue phiên bản 3 (Vue 3) cũng đã ra mắt được 1 năm. Trong 1 năm đó,mình cũng tranh thủ nghe ngóng xem tình hình dân tình dev phản hồi về phiên bản này như thế nào! Đúng như dự đoán của mình, mọi người đón nhận Vue3 rất tích cực. Sau một thời gian kìm nén, đến giờ phút này mình cũng không thể đứng ngoài ngắm nhìn Vue3 được nữa, nên mình quyết định tìm hiểu xem thực hư nó có gì hay ho.

Vue3 là một phiên bản nâng cấp với rất nhiều tính năng được tác giả thêm vào. Trong đó nổi bật nhất là: Composition API, mặc định sử dụng Vite thay cho webpack và hỗ trợ Typescript tốt hơn.

Trong khuôn khổ bài viết này, mình sẽ chỉ tìm hiểu về Composition API và xem nó có tác dụng gì? Ưu điểm của nó so với cách viết ở Vue2 như thế nào?

Mình bắt đầu thôi nhỉ!

Vue Composition API là gì? Tại sao nó lại được tạo ra.

Theo định nghĩa từ nhà phát hành Vue:

Composition API là một tập hợp các API cho phép chúng ta xây dựng các component bằng cách sử dụng các hàm được import (tức là thích cái nào thì import cái đó để dùng) thay vì phải dùng các options (không dùng thì nó vẫn hiện hữu và vẫn bị Vue gọi tới mặc dù bạn không cần tới).

Đọc định nghĩa có vẻ hơi lằng nhằng khó hiểu đúng không? (hầu hết các định nghĩa trong kỹ thuật nó đều lằng ngoằng khó hiểu như vậy). Để hiểu rõ hơn, chúng ta cần hiểu hoàn cảnh lịch sử và tại sao nó lại được tạo ra.

Như trong Vue2, để tạo một component, có phải bạn sẽ code có mẫu như này phải không?

<script>
export default {
  props: {
    query: {type: String, default: ""}
  },
  data() {
    return {
      // Properties for data, filtering, sorting and paging
      filter: {},
      sort: {}
    }
  },
  methods: {
    // Methods for data, filtering, sorting and paging
    search() {
      console.log('do search...')
    },
    sort() {
      console.log('do sort')
    }
  },
  computed:{
    // Values for data, filtering, sorting and paging
  },
  watch:{}
}
</script>
<template>
  <div>...</div>
</template>

Cấu trúc này quá quen thuộc với bạn rồi đúng không? Các options như data(), computed, watch… gọi chung là Option API.

Nhìn vào cấu trúc này, bạn sẽ thấy một nhược điểm lớn, đó là: các phần logic liên quan tới nhau (search, sort…) không được gom nhóm lại mà bị phân mảnh xử lý ở các options khác nhau của Option API. Khi component của chúng ta ngày càng phức tạp. Tưởng tượng với dữ liệu logic, các options của Vue (như props, mounted, computed, updated, …etc) ngày càng phình to ra. Lúc đó, mỗi lần sửa một cái gì đó, bạn sẽ phải cuộn chuột tìm mỏi tay, thật là cực hình.

Hiểu được vấn đề này, các nhà phát triển Vue đã nghĩ tới ý tưởng bằng một cách nào đó chúng ta có thể sắp xếp các phần logic liên quan chỗ nhau vào cùng một chỗ. Câu trả lời chính là Composition API.

Về cơ bản lý thuyết là như vậy, chúng ta sẽ thực hành trên code cho dễ hình dung nhé.

Ví dụ sử dụng Composition API

Giả sử chúng ta có một component về bộ đếm quen thuộc như sau:

<!-- CounterOptionsApi.vue -->
<template>
  <div>
    <h2>Counter Options API</h2>
    <p>Count: {{ count }}</p>
    <p>2^Count: {{ countPow }}</p>
    <button @click="increment()">Increase Count</button>
    <button @click="incrementBy(5)">Increase Count by 5</button>
    <button @click="decrement()">Decrease Count</button>
  </div>
</template>

<script>
export default {
  props: {
    initialValue: {
      type: Number,
      default: 0,
    },
  },
  emits: ['counter-update'],
  data: function () {
    return {
      count: this.initialValue,
    };
  },
  watch: {
    count: function (newCount) {
      this.$emit('counter-update', newCount);
    },
  },
  computed: {
    countPow: function () {
      return this.count * this.count;
    },
  },
  methods: {
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    },
    incrementBy(count) {
      this.count += count;
    },
  },
  mounted: function () {
    console.log('Options API counter mounted');
  },
};
</script>

Component này có những tính năng cơ bản như sau:

  • Chúng ta có một biến data, và biến này được khởi tạo giá trị ban đầu từ props.
  • countPow sẽ là một computed để tạo thuộc tính về lũy thừa.
  • Và một watcher để theo dõi khi biến đếm count thay đổi giá trị
  • Một loạt các method để thay đổi giá trị biến đếm như: tăng, giảm giá trị count…

Nếu bạn chưa từng làm việc với Vue2 và cảm thấy chỗ này chưa hiểu gì thì mình khuyên bạn nên đọc bài viết này về Vue2: Vuejs tutorial cho người mới – Tự xây dựng Todo App

Ok, component này đang làm việc ngon lành rồi. Chúng ta sẽ tiến hành refactoring nó để chuyển sang dùng composition API xem thế nào nhé.

Composition API

Chúng ta sẽ thay đổi cách tiếp cận, sử dụng composition API để viết lại component trên.

Phần <template> hoàn toàn không thay đổi, chúng ta chỉ tập trung vào <script> thôi.

<script>
import { ref, onMounted, computed, watch } from 'vue';

export default {
  props: {
    initialValue: {
      type: Number,
      default: 0,
    },
  },
  emits: ['counter-update'],
  setup(props, context) {
    const count = ref(props.initialValue);

    const increment = () => {
      count.value += 1;
    };
    const decrement = () => {
      count.value -= 1;
    };
    const incrementBy = (value: number) => {
      count.value += value;
    };

    const countPow = computed(() => count.value * count.value);

    watch(count, (value) => {
      context.emit('counter-update', value);
    });

    onMounted(() => console.log('Composition API counter mounted'));

    return {
      count,
      increment,
      decrement,
      incrementBy,
      countPow,
    };
  },
};
</script>

Điểm mới trong cách viết này là thêm một option tên là setup, option này sẽ được chạy trước khi component được tạo ra, sau khi props đã được nhận. Các composition API sẽ được viết trong setup.

Như cách viết trên, chúng ta đã gom tất cả các phần logic vào trong hàm setup. Và chúng đều là các function. Cá nhân mình thấy, các viết này khá giống với hooks trong ReactJS.

Lưu ý: Vì setup được gọi trước khi component được tạo, nên trong setup chúng ta không thể truy cập được bất cứ thành phần nào khác ngoại trừ props.

Ngoài ra, các biến được trả về từ hàm setup, mặc định không phải là reactive.

Để biến chúng thành reactive, bạn cần sử dụng hàm reactive(). Ví dụ

import { reactive, ref } from 'vue';

  const state = reactive({
    count: 0
  })
  console.log(state.count); // 0

  const count = ref(0);
  console.log(count.value); // 0

Đọc đến đây, bạn chắc chắn sẽ có thắc mắc giống mình. Đó à, thế giờ tất cả nhét hết vào hàm setup thì lúc đó hàm setup lại phình to ra, vậy chẳng phải lại giống cách viết cũ? Lại bình mới rượu cũ thôi.

Nhưng thực tế, với các viết theo composition API này, bạn có thể tách code ra thành các file độc lập, sau đó tái sử dụng chúng.

Extract Composition Function

Như mình đã đề cập ở trên, chúng ta sẽ tách phần compositon API thành một hàm độc lập (useCounter)

// useCounter.js
import { ref, computed, onMounted } from 'vue';

export default function useCounter(initialValue) {
  const count = ref(initialValue);

  const increment = () => {
    count.value += 1;
  };
  const decrement = () => {
    count.value -= 1;
  };
  const incrementBy = (value) => {
    count.value += value;
  };

  const countPow = computed(() => count.value * count.value);

  onMounted(() => console.log('useCounter mounted'));

  return {
    count,
    countPow,
    increment,
    decrement,
    incrementBy,
  };
}

Sau đó thì tái sử dụng chúng trong component:

<script
import { watch } from 'vue';
import useCounter from './useCounter';

export default {
  props: {
    initialValue: {
      type: Number,
      default: 0,
    },
  },
  emits: ['counter-update'],
  setup(props, context) {
    const { count, increment, countPow, decrement, incrementBy } = useCounter(
      props.initialValue
    );

    watch(count, (value) => {
      context.emit('counter-update', value);
    });

    return { count, countPow, increment, decrement, incrementBy };
  },
};
</script>

Đã ngon hơn rất nhiều rồi, ít ra không phải super setup function.

Sử dụng Composition API trong Vue2

Composition API là tính năng được tích hợp sẵn trong Vue3, nhưng nếu bạn vẫn muốn sử dụng cho dự án đang chạy Vue2 thì sao?

Bạn vẫn có thể, đó là sử dụng plugin: composition-api. Đây là plugin chính chủ đó nhé.

Tạm kết

Mình hoàn toàn có thể hiểu được rất nhiều devoloper vẫn thích Options API hơn vì nó dễ học hơn, dễ hiểu hơn, đặc biệt là những người mới làm quen Vue. Tất nhiên, với những dự án vừa và nhỏ thì Option API vẫn rất ngon lành, không có gì để chê cả.

Chỉ khi nào dự án lớn, phức tạp và số lượng dev nhiều thì lúc đó Vue Composition API mới phát huy hết tính hiệu quả của nó.

Trên đây là những ý kiến cá nhân của mình, còn bạn thì sao? Bạn  đã sử dụng Vue Composition API chưa? Cảm nhận của bạn như thế nào về tính năng này trong Vue3. Hãy để bình luận bên dưới nhé.

Dịch vụ phát triển ứng dụng mobile giá rẻ - chất lượng
Bài trướcThủ thuật tìm kiếm chuyên sâu trên Google không phải ai cũng biết
Bài tiếp theo5 thủ thuật hữu ích mà tín đồ Android nào cũng nên biết
Tên đầy đủ là Dương Anh Sơn. Tốt nghiệp ĐH Bách Khoa Hà Nội. Mình bắt đầu nghiệp coder khi mà ra trường chẳng xin được việc đúng chuyên ngành. Mình tin rằng chỉ có chia sẻ kiến thức mới là cách học tập nhanh nhất. Các bạn góp ý bài viết của mình bằng cách comment bên dưới nhé !

3
Bình luận. Cùng nhau thảo luận nhé!

avatar
  Theo dõi bình luận  
Mới nhất Cũ nhất Nhiều voted nhất
Thông báo
thang
Guest
thang

Chỗ “Ví dụ sử dụng Composition API” Phải là options API chứ admin ơi

levantan
Guest
levantan

Cách tác giả muốn thể hiện là ví dụ từ : Option Api -> convert sang Composition Api để cho ae dễ hình dung đó mà 😀