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.
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.
Đế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ư update
và componentUpdate
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ỉ ?
Để 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.
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àcomponentUpdated
.oldValue
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
Bài viết hay, tiếc là chưa có ví dụ nào cụ thể =((
Hello anh Quý
; )
Hello e :)))