+20

Bài 2: Tự tạo component và binding data cho component

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 10x,...

Chào các bài đến với bài tiếp theo của mình, ở bài trước mình đã hướng dẫn các bạn Cài đặt Vue và chạy chương trình Hello world đầu tiên. Ở bài này chúng ta sẽ cùng thử tạo một component, khai báo sử dụng và binding data cho nó nhé.

Khai báo mới component

Để khai báo mới một component trước hết chúng ta cần tạo một mới file .vue là component đó. Các bạn mở thư mục resources/js/components, tạo mới một file, đặt tên là MyComponent.vue với nội dung như sau:

<template>
    <div class="my-component">This is my first component</div>
</template>

<script>
    export default {

    }
</script>

<style lang="scss" scoped>
    .my-component {
        color: red;
    }
</style>

Các bạn có thể thấy ở trên mình khai báo một thẻ div với nội dung như trên, cùng với đó bên dưới mình set css cho nó luôn.

Ở series này (2023) ta đang dùng Vue 3, nhưng nếu bạn nào đang dùng Vue 2 thì ta có một số chú ý sau:

  • Vue 2 yêu cầu tất các các dữ liệu trong thẻ <template> phải được để tất cả ở trong một thẻ gọi là root element của component (thường là div). Ví dụ như sau khi compile sẽ báo lỗi:
<template>
    This is my first component
</template>

Ví dụ trên xảy ra lỗi Component template requires a root element, rather than just text vì đoạn text chúng ta để "trần" không là con của một thẻ html nào cả.

  • Hoặc
<template>
    <div class="my-component">This is my first component</div>
    <div class="my-component">This is my first component</div>
</template>

Sẽ báo lỗi Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead. vì chúng ta có tận 2 thẻ là root element

Có tí chú ý cho Vue 2 vậy thôi 😃

Sau khi xong phần component, để sử dụng chúng ta cần khai báo nó trong file app.js, Thêm đoạn code sau vào file app.js:

import './bootstrap';
import { createApp } from 'vue';

const app = createApp({});

import ExampleComponent from './components/ExampleComponent.vue';
import MyComponent from './components/MyComponent.vue';

app.component('example-component', ExampleComponent);
app.component('my-component', MyComponent);

app.mount('#app');

Bằng cách viết như trên chúng ta khai báo component có tên là my-component (tên tuỳ ý) nội dung ở trong file MyComponent.vue, component này được khai báo với scope là global, nghĩa là chúng ta có thể gọi đến chúng ở bất kì một component nào khác trong tương lai mà không cần phải import hay require lại.

Bước cuối cùng là đưa thẻ này vào view Laravel bằng cách chỉnh sửa lại file resources/views/welcome.blade.php như sau:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Laravel</title>

        @vite(['resources/sass/app.scss', 'resources/js/app.js'])
    </head>
    <body class="antialiased">
        <div id="app">
            <my-component></my-component>
        </div>
    </body>
</html>

Nhớ luôn chạy app Laravel và compile Vue bằng 2 command sau nhé:

php artisan serve
npm run dev

Mở http://localhost:8000/, bạn sẽ thấy xuất hiện dòng chữ This is my first component màu đỏ. Thế là ta đã thành công rồi đó.

Screenshot 2023-11-08 at 6.01.26 PM.png

Binding data cho component

Quay lại component MyComponent.vue. Thay vì viết text trực tiếp trong thẻ html chúng ta có thể làm như sau:

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

const msg = ref('Hello World!')
</script>

<template>
  <div class="my-component">{{ msg }}</div>
</template>

<style lang="scss" scoped>
.my-component {
  color: red;
}
</style>

Ở đây trong phần script, chúng ta có khai báo 1 biến msg là 1 cái ref với giá trị là Hello World! và bên dưới phần template ta hiển thị cái ref đó ra. (ref là gì ta sẽ nói ở sau nhé)

Ở phần code HTML ở trên để sử dụng biến msg chúng ta phải gọi nó ở trong cặp thẻ {{ }}. Dữ liệu trong data chúng ta có thể khai báo khá đa dạng: số, mảng, object, string, ...mình sẽ nói dần dần ở các bài sau nhé.

Thử load lại trình duyệt chúng ta sẽ thấy nội dung đã được thay đổi:

Screenshot 2023-11-08 at 6.21.33 PM.png

Tiếp theo ta sẽ tạo 1 button, click vào button thì ta sẽ đổi nội dung của msg đi nhé:

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

const msg = ref('Hello World!')

function updateMsg() {
    msg.value = "My name is James"
}
</script>

<template>
  <div class="my-component">{{ msg }}</div>
  <button @click="updateMsg">
    Update
  </button>
</template>

<style lang="scss" scoped>
.my-component {
  color: red;
}
</style>

Sau đó ta lưu lại, quay trở lại trình duyệt F5 và test thử bấm nút xem có thấy msg đổi ko nhé 😃:

Screenshot 2023-12-23 at 10.59.45 AM.png

Vue 2

Cho bạn nào đang dùng Vue 2, ví dụ tương ứng sẽ là:

<template>
  <div>
    <div class="my-component">{{ msg }}</div>
    <button @click="updateMsg">
      Update
    </button>
  </div>
</template>
  
<script>
export default {
  data() {
    return {
      msg: 'Hello World!'
    }
  },
  methods: {
    updateMsg() {
      this.msg = "My name is James"
    }
  }
}
</script>
  
<style lang="scss" scoped>
.my-component {
  color: red;
}
</style>

Ở trên trong phần script, chúng ta có thêm phần data, nó return về một biến dạng key-value là message có giá trị là This is my first component using binding data (Sau này toàn bộ data của component chúng ta đều phải để ở trong return nhé).

Với Vue 2 thì ta thường chỉ dùng 1 cách này để khai báo data, với Vue 3 thì ta có nhiều cách đó nhé

Nếu các bạn để ý thì với Vue 2 mình phải thêm 1 thẻ <div> ở chỗ template để bọc ngoài tất cả các nội dung bên trong. Vì với Vue 2 thì nó ko support nhiều root tag bên trong <template>

methods là gì ta sẽ nói kĩ hơn ở bài sau nhé, nó cũng xêm xêm. như function bình thường thôi 😉

Format tí cho đẹp

Khi code thì các bạn nhớ là luôn đảm bảo code của ta được format rõ ràng, lên xuống dòng, space tab đầy đủ, nếu không sẽ hại mắt mình và người khác lắm nhé 😄.

Nếu các bạn đang dùng VSCode và đã cài sẵn 2 extensions (Volar) mà mình nói ở bài đầu thì cách format rất đơn giản. Ta chuột phải vào bất kì đâu trong file và chọn Format Document.

Screenshot 2023-11-08 at 6.11.06 PM.png

Ta nên làm việc này khi ta code xong bất kì dòng nào nhé các bạn, làm liên tục cho quen, luôn tay luôn chân 😃. Dùng tổ hợp phím cho dễ nhé ALT+SHIFT+F

Vọc vạch

Reactive là gì?

Ở ví dụ trên các bạn có thể thấy là ta có 1 cái state là msg, khi state đó thay đổi thì trên UI HTML cũng thấy được cập nhật theo mà ta không cần phải dùng tới những API của JS kiểu như là .innerHTML = "My name is James" hoặc là dùng JQuery để làm điều đó. Và với state như vậy ta gọi nó là reactive state - state mà nó có tính phản hồi, ý là state phía JS thay đổi thì UI HTML cũng sẽ được update theo, phản ánh đúng cái state hiện tại.

Các bạn khi xem docs của các UI framework có thể đã (và sẽ) gặp người ta nói rất nhiều về reactive hay reactivity (danh từ của reactive). Hi vọng là các bạn sẽ không thấy bối rối lần tiếp theo nhìn thấy nó 😉

Nhưng như các bạn thấy giờ đây tất cả đều được xử lý bởi Vue hết, ta không cần quan tâm (lắm) có gì xảy ra bên dưới, đặc biệt khi vào dự án thực tế khi phải build những app lớn hơn ta sẽ thấy những framework hiện nay Vue/React/Angular - chúng giúp ta tăng tốc quá trình phát triển UI lên rất rất nhiều rồi ấy 😃

ref()

Như mình đề cập ở phần trước, với Vue 2 thì hầu như ta chỉ dùng 1 cách để khai báo reactive state là dùng data(), nhưng với Vue 3 ta có nhiều hơn 2 cách 😃, tuỳ vào cái ta cần là gì. Và 2 trong số những cách phổ biến nhất ở Vue 3 đó là dùng ref()reactive()

Ở ví dụ bài này các bạn thấy là mình dùng ref, với ref ta có thể khai báo bất kì thứ gì là reactive state: string, number, boolean, array, Map,...

Truy cập tới ref ở <template>: ta dùng trực tiếp ref đó {{ msg }}

Truy cập tới ref ở <script>: ta dùng msg.value

Chú ý rằng state khai báo với ref là deeply reactive, tức là mọi phần tử con đều reactive hết. Ví dụ các bạn có 1 object trong đó ta có nhiều object con lồng nhau nhiều cấp (nested), thì bất kì cấp nào cũng đều reactive cả (thay đổi thì UI thay đổi theo)

reactive()

Với Vue 3 một cách khác để khai báo reactive state là dùng reactive().

Ta sửa lại ví dụ chút nhé:

<script setup>
import { reactive } from 'vue'

const state = reactive({ msg: 'Hello World!'})

function updateMsg() {
  state.msg = "My name is James"
}
</script>

<template>
  <div class="my-component">{{ state.msg }}</div>
  <button @click="updateMsg">
    Update
  </button>
</template>

<style lang="scss" scoped>
.my-component {
  color: red;
}
</style>

Quay lại trình duyệt F5 và ta được điều tương tự.

Ở đây ta thấy rằng, với reactive() thì ta luôn phải truyền vào state kiểu object: Object, Array, Map,.... Ta không thể truyền trực tiếp các dạng kiểu như string, number, boolean...

Ví dụ sau sẽ không chạy:

const state = reactive('Hello World!')

Một số hạn chế khi dùng reactive():

  • reactive chỉ dùng được với data có kiểu object
  • ko được thay thế (gán lại) bằng state mới, ví dụ:
let state = reactive({ count: 0 })

// state ko còn reactive
state = reactive({ count: 1 })
  • cần phải để ý khi destruct hay truyền vào function/method:
const state = reactive({ count: 0 })

// count này ko reactive
let { count } = state
// tăng count ở đây ko ảnh hưởng gì đến state và UI cũng sẽ ko cập nhật
count++

// ở đây thực tế là ta truyển vào số 0
// và hoàn toàn ko có gì reactive cả
// nên nếu trong function ta thay đổi count
// thì UI cũng ko cập nhật
callSomeFunction(state.count)

Dùng ref() hay reactive()?

Từ những ví dụ trên, câu hỏi đặt ra là dùng ref hay reactive, khi nào nên dùng cái nào?

Với những hạn chế và chú ý khi sử dụng reactive, do vậy cách được khuyên dùng là ta nên chủ yếu sử dụng ref()để khai báo reactive state, tránh việc nhầm lẫn hay bug do sử dụng reactive() ko đúng.

Tất nhiên đây chỉ là lời khuyên và nếu các bạn muốn dùng hết reactive() thì cũng ko sao cả, miễn là ta luôn biết ta đang làm gì 😃

Nếu bạn đã dùng framework khác (React, Angular,...)

Vì hiện tại có rất nhiều library/framework, do vậy có thể có những từ/cụm từ giống nhau, nhưng ý nghĩa lại rất khác nhau nên các bạn cần phải phân biệt rõ ràng tránh hiểu lầm nhé.

Bên React, useRef: sẽ cho ta một cái ref để lưu data nào đó, nhưng data này ko reactive, tức là khi nó thay đổi thì UI sẽ ko tự cập nhật ngay đâu.

Bên Angular: ta cũng có ElementRef, ViewContainerRef, ChangeDetectorRef,... ti tỉ loại Ref, nhưng chúng hoạt động khác nhau lắm nhé.

Nhiều khi ta phải làm nhiều project mỗi project một framework khác nhau, vậy nên dùng cái nào ta phải hiểu rõ nó nhé, tránh hiểu lầm 😃

shallowRef(), shallowReactive()

Như mình đã đề cập ở trên khi ta khai báo reactive state với Vue dùng ref() hay reactive() thì ở mọi cấp của 1 state lồng nhau sẽ đều là reactive hết

const msg = ref({
  a: {
    b: {
      c: {
        d: 1
      }
    }
  }
})

Tức là bất kì cấp nào thay đổi thì Vue sẽ đều cập nhật lại UI.

Nhưng trong 1 số trường hợp thì state của ta có thể rất phức tạp, lồng nhau nhiều cấp, nếu cứ cấp nào thay đổi cũng cập nhật UI thì có thể sẽ nặng và gây ra tình trạng "lag".

Với những trường hợp như vậy thì ta có thể dùng shallowRef hoặc shallowReactive

const state = shallowRef({ count: 1 })

// ko thay đổi UI
state.value.count = 2

// chỉ khi gán lại bằng object mới sẽ cập nhật
state.value = { count: 2 }

//-------------------------------------

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})

// cập nhật UI (cấp đầu tiên sẽ reactive)
state.foo++

// ko reactive, ko cập nhật UI
state.nested.bar++

Kết

Phùuuuuuu, cuối cùng cũng hết bài 🤣🤣🤣.

Qua bài này hi vọng rằng các bạn đã hiểu về cách khai báo và sử dụng reactive state qua đó áp dụng giải pháp hợp lý nhất vào project của mình.

Ở bài tiếp theo chúng ta sẽ sử dụng Vue.js devtools để có thể theo dõi các quá trình thay đổi dữ liệu ở các component VueJS nhé.

Cám ơn các bạn đã theo dõi và hẹn gặp lại các bạn ở những bài sau ^^!


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í