- Học Vue
-
Hệ sinh thái
Hỗ trợ
Công cụ
Thư viện chính thức
Tin tức
Tài nguyên
- Đội ngũ
- Hỗ trợ Vue
- Ngôn ngữ
Hướng dẫn
Khái niệm cơ bản
- Cài đặt
- Giới thiệu
- Đối tượng Vue
- Cú pháp template
- Computed property và watcher
- Binding cho class và style
- Render theo điều kiện
- Render danh sách
- Xử lí sự kiện
- Ràng buộc form input
- Cơ bản về component
Components In-Depth
- Đăng kí Component
- Props
- Custom Events
- Slots
- Dynamic & Async Components
- Handling Edge Cases
Hiệu ứng chuyển động
- Transition cho enter/leave & danh sách
- Transition cho trạng thái
Tái sử dụng & kết hợp
- Mixin
- Directive tùy biến
- Các hàm render & JSX
- Plugin
- Filter
Công cụ
- Triển khai cho môi trường production
- Single File Components
- Unit test
- TypeScript Support
Mở rộng quy mô ứng dụng
- Routing
- Quản lí trạng thái
- Render ở phía server
Bên trong Vue
- Reactivity in Depth
Thông tin thêm
- Comparison with Other Frameworks
- Join the Vue.js Community!
- Đội ngũ
Các hàm render & JSX
Cơ bản
Trong đại đa số các trường hợp, Vue khuyến khích sử dụng template để xây dựng HTML. Tuy nhiêu có một số trường hợp bạn cần dùng đến sức mạnh của JavaScript. Những lúc này bạn có thể dùng hàm render (render function), một giải pháp gần hơn với trình biên dịch, để thay thế cho template.
Chúng ta hãy xem một ví dụ đơn giản trong đó một hàm render
trở nên hữu ích. Chẳng hạn, bạn muốn tạo các tiêu đề h1
, h2
, h3
… chứa liên kết, như sau:
<h1> |
Từ đoạn HTML trên, bạn quyết định tạo một giao diện component như sau:
<anchored-heading :level="1">Hello world!</anchored-heading> |
Khi bắt đầu với một component chỉ tạo thẻ tiêu đề dựa trên prop level
được truyền vào, bạn sẽ nhanh chóng đi đến một kết quả trông như thế này:
<script type="text/x-template" id="anchored-heading-template"> |
Vue.component('anchored-heading', { |
Rõ ràng đây không phải là một template tốt. Chẳng những nó quá rườm rà, mà ở đây chúng ta còn lặp lại <slot></slot>
cho mỗi level, và lại phải thực hiện một quá trình tương tự khi thêm phần tử <a>
. Vì thế, hay thử viết lại với một hàm render
:
Vue.component('anchored-heading', { |
Đơn giản hơn nhiều! Đại để thế. Code trở nên ngắn hơn, nhưng cũng đòi hỏi bạn phải quen thuộc hơn với các thuộc tính của đối tượng Vue. Trong trường hợp này, bạn phải biết rằng khi truyền các phần tử con không có thuộc tính slot
vào trong một component, ví dụ Hello world!
trong anchored-heading
, các phần tử con này được chứa trong đối tượng component tại $slots.default
. Chúng tôi khuyên bạn nên đọc về các API của các thuộc tính của đối tượng Vue trước khi đi sâu nghiên cứu về các hàm render.
Node, tree, và virtual DOM
Trước khi đi sâu vào các hàm render, ít nhiều kiến thức về cách hoạt động của trình duyệt là rất quan trọng. Ví dụ chúng ta có đoạn HTML sau:
<div> |
Khi đọc đoạn code này, trình duyệt sẽ xây dựng cấu trúc dạng cây (DOM tree) bao gồm các “DOM node” để giúp quản lí mọi thứ, tương tự như việc bạn xây dựng một cây gia phả để giữ thông tin về mọi người trong dòng họ vậy.
Cấu trúc cây của đoạn HTML trên sẽ giống như sau:
Mỗi phần tử trên DOM tree là một node. Mỗi text là một node. Ngay cả comment cũng là node! Một node đơn giản chỉ là một “mảnh” trên trang web. Và cũng tương tự như trong một cây gia phả, mỗi node có thể có các node con (nghĩa là một mảnh có thể chứa các mảnh khác).
Cập nhật tất cả các node này một cách hiệu quả có thể là một việc khó khăn, nhưng may thay, bạn không bao giờ phải làm việc này một cách thủ công. Thay vào đó, chỉ cần báo cho Vue biết bạn muốn có HTML gì trên trang, trong một template:
<h1>{{ blogTitle }}</h1> |
hoặc trong một hàm render:
render: function (createElement) { |
Và trong cả hai trường hợp, Vue sẽ tự động giữ cho trang web được cập nhật, ngay cả khi blogTitle
thay đổi.
Virtual DOM
Vue làm được điều này nhờ vào việc xây dựng một virtual DOM (DOM ảo) để theo dõi tất cả những thay đổi cần thực hiện đối với DOM thật. Bạn hãy nhìn kĩ dòng này:
return createElement('h1', this.blogTitle) |
Lệnh createElement
ở đây thực chất là đang trả về cái gì? Không hẳn là một element (phần tử) DOM thật sự. Nếu nói cho đúng, chúng ta có thể đặt lại tên cho hàm này một cách chính xác hơn là tạoMôTảChoNode
, vì nó chứa những thông tin mô tả node mà Vue cần biết để render, bao gồm cả mô tả cho các node con. Chúng ta gọi mô tả này là một “virtual node” (node ảo), thường viết tắt là VNode. “Virtual DOM” là tên của toàn bộ một cây các VNode, được xây dựng từ một cây các component Vue.
Các tham số của createElement
Điều tiếp theo mà bạn cần thông thạo là cách dùng các tính năng của template trong hàm createElement
. Đây là danh sách các tham số mà createElement
nhận:
// @returns {VNode} |
Chi tiết về data object
Một điểm cần lưu ý: tương tự với việc được đối xử đặc biệt trong template, v-bind:class
và v-bind:style
cũng có các field (trường) riêng ở top-level (cấp cao nhất) trong data object của VNode. Object này cũng cho phép bạn bind (ràng buộc) các thuộc tính HTML thông thường cũng như các thuộc tính DOM như innerHTML
(thay thế cho directive v-html
):
{ |
Ví dụ hoàn chỉnh
Với kiến thức đã học, bây giờ chúng ta đã có thể viết nốt component trên đây:
var getChildrenTextContent = function (children) { |
Một số hạn chế
VNode phải là duy nhất
Tất cả các VNode trong cây component phải là duy nhất. Điều này có nghĩa là hàm render sau đây không hợp lệ:
render: function (createElement) { |
Nếu thật sự muốn dùng cùng một phần tử hoặc component nhiều lần, bạn có thể dùng một hàm factory. Ví dụ, hàm render sau đây là một cách hoàn toàn hợp lệ để render 18 phần tử <p>
giống nhau:
render: function (createElement) { |
Thay thế các tính năng của template bằng JavaScript đơn thuần
v-if
và v-for
Bất cứ khi nào một việc gì đó có thể làm được dễ dàng bằng JavaScript đơn thuần, các hàm render của Vue đều không cung cấp một giải pháp thay thế chuyên biệt. Ví dụ, trong một template có sử dụng v-if
và v-for
:
<ul v-if="items.length"> |
Ví dụ này có thể được viết lại với if
/else
và map
của JavaScript trong một hàm render:
props: ['items'], |
v-model
Vue không cung cấp tính năng thay thế cho v-model
trong các hàm render - bạn sẽ phải tự phát triển logic này:
props: ['value'], |
Đây là cái giá cho việc viết code ở cấp thấp – tuy nhiên bù vào đó thì bạn có gần như toàn quyền điều khiển các tương tác nếu so sánh với v-model
.
Event và key modifier
Đối với các event modifier .passive
, .capture
và .once
, Vue cung cấp các prefix (tiền tố) có thể sử dụng với on
:
Modifier | Prefix |
---|---|
.passive |
& |
.capture |
! |
.once |
~ |
.capture.once hoặc.once.capture |
~! |
Ví dụ:
on: { |
Đối với tất cả các event và key modifier khác, một prefix chuyên biệt là không cần thiết vì bạn có thể dùng các phương thức sự kiện bên trong hàm xử lí:
Modifier | Giải pháp tương đương trong hàm xử lí |
---|---|
.stop |
event.stopPropagation() |
.prevent |
event.preventDefault() |
.self |
if (event.target !== event.currentTarget) return |
Phím:.enter , .13 |
if (event.keyCode !== 13) return (thay 13 bằng một mã phím khác đối với các modifier khác) |
Modifier cho phím:.ctrl , .alt , .shift , .meta |
if (!event.ctrlKey) return (thay ctrlKey bằng altKey , shiftKey , hoặc metaKey tương ứng) |
Đây là một ví dụ dùng tất cả các modifier trên cùng một lúc:
on: { |
Slot
Bạn có thể truy xuất đến các nội dung tĩnh của slot dưới dạng các mảng VNode thông qua this.$slots
:
render: function (createElement) { |
và truy xuất đến các scoped slot dưới dạng các hàm trả về VNode thông qua this.$scopedSlots
:
props: ['message'], |
Để truyền các scoped slot vào một component con bằng hàm render, dùng trường scopeSlots
trong dữ liệu của VNode:
render: function (createElement) { |
JSX
Nếu dùng nhiều hàm render
, có lẽ bạn sẽ cảm thấy khá cực nhọc khi phải viết những đi viết lại những dòng code như thế này:
createElement( |
Nhất là khi nếu dùng template thì đơn giản hơn nhiều:
<anchored-heading :level="1"> |
Đó là lí do Vue cung cấp một plugin dành cho Babel để dùng JSX với Vue, giúp chúng ta quay lại sử dụng một cú pháp gần gũi hơn với template:
import AnchoredHeading from './AnchoredHeading.vue' |
Dùng h
thay cho createElement
là một quy ước thông dụng trong hệ sinh thái của Vue, đồng thời là bắt buộc đối với JSX. Nếu h
không tồn tại trong scope, ứng dụng của bạn sẽ xảy ra lỗi.
Để biết thêm chi tiết về cách thức đối chiếu từ JSX sang JavaScript, hãy đọc kĩ hướng dẫn sử dụng trước khi dùng.
Functional component
Component mà ta vừa viết còn khá đơn giản - nó không quản lí trạng thái, không theo dõi trạng thái được truyền vào, và không có bất kì phương thức vòng đời (life-cycle method) nào. Thật sự nó chỉ là một hàm với vài thuộc tính (prop).
Trong những trường hợp như thế, ta có thể đánh dấu component là functional
. Một functional component (component thuần chức năng) không có trạng thái (stateless – không có data
), không có đối tượng (instanceless – không có ngữ cảnh this
), và trông như thế này:
Vue.component('my-component', { |
Trong các phiên bản trước 2.3.0, tùy chọn
props
là bắt buộc nếu bạn muốn nhận props trong một functional component. Từ phiên bản 2.3.0 trở về sau, bạn có thể bỏ qua tùy chọnprops
, và khi đó tất cả các thuộc tính tìm thấy trên component node sẽ được trích xuất ngầm thành props.
Từ bản 2.5.0 trở đi, nếu bạn đang dùng single-file component, functional component dựa trên template có thể được khai báo như sau:
<template functional> |
Mọi thứ mà component cần được truyền vào thông qua context
, một object chứa:
props
: Một object chứa các prop được cung cấpchildren
: Một mảng các VNode conslots
: Một hàm trả về một object chứa các slotdata
: Toàn bộ object data truyền vào componentparent
: Trỏ đến component chalisteners
: (2.3.0+) Một object chứa các hàm lắng nghe sự kiện được đăng kí ở component cha. Đây là một tên khác củadata.on
.injections
: (2.3.0+) Chứa các injection đã được resolve (phân giải), nếu sử dụng tùy chọninject
Sau khi thêm functional: true
vào component AnchorHeading
, chúng ta cần thêm những thay đổi sau đây vào hàm render của component này: thêm tham số context
, thay this.$slots.default
bằng context.children
, và thay this.level
bằng context.props.level
.
Vì chỉ là hàm đơn thuần, việc render các functional component ít tốn kém nhiều so với component thông thường. Tuy nhiên, việc thiếu một đối tượng bền vững (persistent instance) nghĩa là bạn sẽ không thấy được functional component trong cây component của Vue devtools.
Functional component cũng rất hữu dụng trong vai trò wrapper component. Ví dụ, khi bạn cần:
- Chọn một trong số vài component khác thông qua code để delegate (ủy nhiệm)
- Chỉnh sửa các phần tử con, props, hoặc data trước khi truyền vào một component con
Sau đây là một ví dụ về một component smart-list
, component này đóng vai trò “delegate” cho các component cụ thể hơn dựa trên các prop được truyền vào:
var EmptyList = { /* ... */ } |
Truyền thuộc tính và sự kiện xuống phần tử hoặc component con
Với những component thông thường, các thuộc tính (attribute) không được định nghĩa dưới dạng prop sẽ được thêm vào phần tử gốc (root element) của component, thay thế hoặc merge một cách thông minh các thuộc tính trùng tên có sẵn.
Tuy nhiên, các component chức năng bắt buộc bạn phải định nghĩa minh bạch hành vi này:
Vue.component('my-functional-button', { |
Bằng cách truyền context.data
làm tham số thứ hai cho createElement
, chúng ta đang truyền xuống bất cứ thuộc tính hoặc hàm lắng nghe sự kiện nào được dùng trong my-function-button
. Việc này minh bạch đến mức các sự kiện này thậm chí không cần modifier .native
.
Nếu đang dùng component chức năng dựa trên template, bạn cũng sẽ phải tự thêm vào các thuộc tính và hàm lắng nghe sự kiện. Vì có quyền truy xuất đến các nội dung ngữ cảnh riêng biệt, chúng ta có thể dùng data.attrs
để truyền xuống các thuộc tính HTML và dùng listeners
(alias của data.on
) để truyền xuống các hàm lắng nghe sự kiện.
<template functional> |
So sánh giữa slots()
và children
Bạn có thể tự hỏi tại sao chúng ta lại cần cả hai slots()
và children
. Chẳng phải slots().default
và children
là như nhau hay sao? Trong một số trường hợp thì điều này là đúng, tuy nhiên hãy xem một functional component với các phần tử con như sau:
<my-functional-component> |
Trong component này, children
sẽ chứa cả hai phần tử <p>
, slots().default
chỉ chứa phần tử thứ hai, vàslots().foo
chỉ chứa phần tử thứ nhất. Việc có cả hai children
và slots()
vì vậy sẽ cho phép bạn quyết định component này có biết về hệ thống slot hay không, hoặc có thể giao phó trách nhiệm này cho một component khác bằng cách truyền children
.
Biên dịch template
Có thể bạn muốn biết là các template của Vue thật ra là được biên dịch thành các hàm render. Đây là một chi tiết kĩ thuật nâng cao mà thông thường có lẽ bạn không cần phải quan tâm đến, tuy nhiên nếu muốn xem các tính năng template cụ thể được biên dịch như thế nào thì bạn có thể thấy việc đó khá thú vị. Bên dưới là một ví dụ nhỏ sử dụng Vue.compile
để biên dịch một chuỗi template thành hàm render tại chỗ:
{{ result.render }}
_m({{ index }}): {{ fn }}
{{ result.staticRenderFns }}
{{ result }}