Tất tần tật về Vue-directive và custom directive

Nếu như các bạn làm quen với vue js thì chắc hẳn các bạn đã dùng đến vue-directive rồi mà không nhận ra. Ví dụ như v-if, v-else, v-text, v-model, v-for, … Đây đều là những directive mặc định mà vue cung cấp sẵn. Các bạn có thể lên trang chủ Vue đọc là hiểu rõ nhất. Ngoài ra, chúng ta còn có thể tự custom các directive cho mình.

Trong bài viết này, tôi sẽ nói cụ thể hơn về cách thứ vue-directive hoạt động. Bắt đầu thôi nào

1. Đăng ký vue-directive và các hàm hook

Đăng ký một vue-directive rất đơn giản.

import Vue from 'vue'
// Khi đăng kí một custom directive thì tiền tố của nó luôn là 'v-'
// với trường hợp này thì tên custom directive của tôi là `v-log`
Vue.directive('log', {
  // Khi phần tử liên quan được thêm vào DOM...
  inserted: function (el) {
    // log ra thẻ đang sử dụng directive
    console.log(el)
  }
})
<template>
  <div>
    <input v-log />
  </div>
</template>

Khi đăng kí directive thì Vue cung cấp cho chúng ta các hook của một directive như bind, inserted, update, … Nó cũng khá giống lifecycle trong component vậy.

các hook trong directive
các hook trong directive

bind: Được gọi đầu tiên và chỉ gọi duy nhất khi directive được liên kết với element

inserted: Được gọi sau bind và chỉ gọi duy nhất khi phần tử được chèn vào DOM mẹ.

update: Được gọi khi dữ liệu của element thay đổi, nhưng các vnode khác ở thời điểm này vẫn chưa được cập nhật.

componentUpdated:  Hook này được gọi khi dữ liệu element và các vnode liên quan đều được cập nhật.

unbind: Hook này cũng chỉ được gọi 1 lần khi directive được hủy bỏ.

VNode ở đây hiểu là virtual node.

Dưới đây mình có một ví dụ:

import Vue from 'vue'

Vue.directive("log", {
  bind(el, bind, vnode) {
    console.log(1, "--bind--");
  },
  inserted: function(el, bind, vnode) {
    console.log(2, "--inserted--");
  },
  update: (el, bind, vnode, oldVnode) => {
    console.log(3, "--update--");
  },
  componentUpdated: function(el, bind, vnode, oldVnode) {
    console.log(4, "--componentUpdated--");
  },
  unbind: function(el, bind, vnode) {
    console.log(5, "--unbind--");
  }
});
<template>
  <div>
    <div v-if="isShow">
      <input v-model="text" v-log />
      <p>{{ text }}</p>
    </div>
    <button @click="isShow = !isShow" v-text="isShow ? 'Close' : 'Show'" />
  </div>
</template>
<script>
export default {
  data() {
    return {
      text: "",
      isShow: false
    };
  },
};
</script>

Khi click để show ra input thì ta nhận thấy hàm bind chạy trước sau đó mới đến inserted. Và lúc click close thì thấy hàm unbind được gọi.

 hook bind, inserted và unbind chạy
hook bind, inserted và unbind chạy

Đến đây rồi có ai thắc mắc giống tôi lúc đầu không, khi tạo directive mới và test các hook như updatecomponentUpdate thấy chúng nó chạy đều cùng 1 lúc không. Ủa? vậy thực sự 2 thằng này khác nhau như nào nhỉ ?

 hook update và componentUpdated được chạy khi thay đổi dữ liệu ở input
hook update và componentUpdated được chạy khi thay đổi dữ liệu ở input

Để giải thích việc này, mình sẽ thử debugger ở hàm update để xem sự khác nhau rõ ràng dễ hiểu hơn.

đặt debugger để check xem khác nhau giữa 2 cái hook này
đặt debugger để check xem khác nhau giữa 2 cái hook này

Có thể rõ ràng nhìn thấy, khi giá trị text thay đổi, thì:

update: Các VNode chính đã được cập nhật nhưng các VNode khác thì vẫn chưa được cập nhật.

componentUpdate: VNode chính lẫn các VNode khác đều đã được cập nhật.

2. Các tham số Trong các hàm hook ?

el: Element đang sử dụng directive, là phần tử được directive bind vào, có thể sử dụng thay đổi DOM trực tiếp.

binding: Một object chứa nhiều thuộc tính:

  • name: Tên của directive
  • value: Giá trị được truyền vào directive. Ví dụ với v-log="1 + 1" thì value sẽ là 2
  • expression: Biểu thức của binding dưới dạng chuỗi. Ví dụ với v-log="1 + 1", thì expression sẽ là "1 + 1"
  • arg: Tham số được truyền vào directive, nếu có. Ví dụ với v-log:foo thì arg sẽ là "foo".
  • modifiers: Một object chứa các modifier, nếu có. Ví dụ với v-log.foo.bar, thì modifiers sẽ là { foo: true, bar: true }.
  • oldValue: Giá trị trước đây, chỉ tồn tại trong các hook update và componentUpdatedoldValue sẽ luôn có cho dù giá trị có được thay đổi hay không

vnode: Vnode được trình biên dịch của Vue tạo ra

oldVnode: VNode trước đây, chỉ tồn tại trong các hook update và componentUpdated

3. Khác nhau giữa Vue 2.x và Vue 3.x khi custom directive

Từ đầu đến giờ tôi đã đề cập directive của Vue 2.x rồi cho nên đến chỗ này tôi xin phép được nói về phiên bản Vue 3.x nhiều hơn.

Ở bản Vue 3.x này, thì có thêm một vài hàm hook và cũng như lược bỏ vài hàm ở Vue 2.x.

Như thêm hàm hook created được gọi trước khi các thuộc tính hoặc các sự kiện của element được thực hiện. Hay là đổi tên hàm bind thành beforeMount

Tóm tắt lại thì custom directive của Vue 3.x nó sẽ trông như này

const app = Vue.createApp({})
app.directive('log', {
  created(el, binding, vnode, prevVnode) {}, // new. được gọi trước khi các thuộc tính hoặc các sự kiện của element được thực hiện
  beforeMount() {},   // đổi tên bind -> beforeMount
  mounted() {},       // đổi tên inserted -> mounted 
  beforeUpdate() {},  // new. Được gọi trước khi chính phần tử đó được cập nhật
  updated() {},       // đổi tên componentUpdated  thành updated
  beforeUnmount() {}, // new. Giống như lifecycle, hook này được chạy trước khi directive bị xóa
  unmounted() {}      // đổi tên từ unbind -> unmounted
})

Nhìn qua thì có thể thấy các hàm hook giờ đây khá giống lifecycle của component. Và tôi ủng hộ điều này, thú thực thì nhìn nó rất trực quan và rất dễ nhớ.

Các bạn có để ý, họ đã xóa bỏ hàm update vì có quá nhiều điểm tương đồng với updated nên họ cho rằng việc này là quá dư thừa, không cần thiết.

Một điều nữa là cách truy vấn một instance component ở 2 phiên bản có sự khác nhau

// Vue 2.x
bind(el, binding, vnode) {
  const vm = vnode.context
}
// Vue 3.x
mounted(el, binding, vnode) {
  const vm = binding.instance
}

Tài liệu tham khảo

https://vuejs.org/v2/guide/custom-directive.html

https://v3.vuejs.org/guide/migration/custom-directives.html

You may also like...

3 Responses

  1. Minh Hoàng viết:

    Bài viết hay, tiếc là chưa có ví dụ nào cụ thể =((

  2. Ngọc viết:

    Hello anh Quý
    ; )

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *