Truyền dữ liệu vào Vue.js component

Điểm lại một số vấn đề của bài trước, component trong Vue có thể được tạo ra với phương thức Vue.component() với đầu vào là một đối tượng chứa các tùy chọn như data, template… Trong template có thể sử dụng dữ liệu đã được khai báo trong data của component và không thể sử dụng được các dữ liệu được đăng ký trong data của Vue instance cũng như của các component khác một cách trực tiếp.

1. Phạm vi dữ liệu của component

Chúng ta cùng xem xét một ví dụ tạo ra một Hello component và component này sẽ lấy dữ liệu tên người dùng để hiển thị câu chào.

<!DOCTYPE html>
<html>
<head>
    <title>Phạm vi dữ liệu Component Vue.js - Example 1 - Allaravel.com</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
</head>
<body>
    <template id="hello-template">
        <h1>{{ message }} {{ userName }}</h1>
    </template>
    <div class="container" id="app">
        <hello-component></hello-component>
    </div>
    <script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
    <script type="text/javascript">
        Vue.component('hello-component',{
            data: function () {
                return {
                    message: 'Xin chào'
                }
            },
            template: '#hello-template'
        })
        new Vue({
            el: '#app',
            data: {
                userName: 'Allaravel'
            }
        });
    </script>
</body>
</html>

Chúng ta kỳ vọng rằng màn hình sẽ hiển thị câu chào “Xin chào Allaravel” nhưng không nó chỉ hiển thị “Xin chào” mà không in ra tên. Mở tab console lên xem có lỗi gì không?

Phạm vi vue component

Chúng ta thấy đấy lỗi thông báo như sau: “Thuộc tính userName không được khai báo nhưng vẫn tham chiếu đến khi render”. Như vậy trong template chỉ được tham chiếu đến các thuộc tính dữ liệu được khai báo trong data của component? Đúng vậy, template chỉ có thể tham chiếu đến các thuộc tính trực tiếp nếu thuộc tính đó của component. Với các thuộc tính, dữ liệu ở phạm vi ngoài component chúng ta phải sử dụng đến một cách khác sẽ được giới thiệu trong phần tiếp theo.

2. Truyền dữ liệu vào một component

Nếu các bạn đã đọc phần cơ bản về component, bạn đã quen với cách đặt vấn đề là component là một khối mã nguồn bao gồm cả HTML, Javascript, CSS giống như function trong các ngôn ngữ lập trình. Vậy nó cũng cần có các tham số để có thể truyền dữ liệu từ ngoài vào trong khối mã nguồn đó. Vue.component() làm việc này thông qua thuộc tính props. Truyền dữ liệu vào một component bao gồm 2 bước như sau:

  • Bước 1: Khai báo các thuộc tính có thể tham chiếu trong template với tùy chọn props.
  • Bước 2: Truyền dữ liệu cho thuộc tính khai báo với v-bind khi sử dụng component.

Ví dụ Hello component ở phần đầu sẽ được hoàn thiện lại như sau:

<!DOCTYPE html>
<html>
<head>
    <title>Truyền dữ liệu vào Component Vue.js với props - Example 1 - Allaravel.com</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
</head>
<body>
    <template id="hello-template">
        <h1>{{ message }} {{ userName }}</h1>
    </template>
    <div class="container" id="app">
        <hello-component v-bind:user-name="userName"></hello-component>
    </div>
    <script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
    <script type="text/javascript">
        Vue.component('hello-component',{
            data: function () {
                return {
                    message: 'Xin chào'
                }
            },
            props: ['userName'],
            template: '#hello-template'
        })
        new Vue({
            el: '#app',
            data: {
                userName: 'Allaravel'
            }
        });
    </script>
</body>
</html>

Code điều chỉnh này chỉ khác một chút so với ví dụ đầu tiên:

Thay đổi thứ nhất: Khai báo thuộc tính tham chiếu đến từ bên ngoài trong tùy chọn props

props: ['userName']

Thay đổi thứ hai: Truyền dữ liệu vào thuộc tính user-name khi sử dụng hello-component

<hello-component v-bind:user-name="userName"></hello-component>

Bạn đang có một thắc mắc nhỏ, tại sao là v-bind:user-name mà không phải v-bind:userName? Do các thuộc tính trong HTML là không phân biệt hoa thường (case insensitive), như vậy trình duyệt sẽ thông dịch các ký tự viết hoa cũng như các ký tự viết thường. Do đó, khi bạn sử dụng các template với các thành phần DOM, các prop có tên kiểu camelCase cần được chuyển sang tương đương với dạng kebab-cased. Xem thêm Các chuẩn đặt tên trong lập trình.

Chú ý, khi thuộc tính ở ngoài component thay đổi, thì nó ngay lập tức sẽ cập nhật trong component, tuy nhiên không thể từ trong component mà thay đổi được giá trị thuộc tính ở ngoài này. Luồng dữ liệu như vậy gọi là one-way-down binding.

3. Các dạng dữ liệu có thể truyền vào một component

Trong ngôn ngữ HTML, khi muốn truyền một giá trị cho thuộc tính của thẻ HTML nào đó ta chỉ việc dùng cú pháp:

<html-tag property="value">

Với framework Vue.js chúng ta có thể sử dụng cả cách thông thường như trên hoặc sử dụng v-bind, hai cách có một số điểm khác nhau.

<html-tag v-bind:property-a="propertyB">
Chú ý: v-bind:property có thể viết ngắn gọn thành :property
<html-tag :property-a="propertyB">

Chắc bạn còn nhớ v-bind được sử dụng để gán một biểu thức Javascript vào một thuộc tính của thẻ HTML. Do vậy, khi bạn sử dụng v-bind:my-number=”78″ thì 78 được hiểu như là một biểu thức Javascript hơn là một chuỗi. Với cách truyền giá trị kiểu HTML thì giá trị truyền vào sẽ là một chuỗi thông thường.

3.1 Truyền một số vào component

<blog-post v-bind:likes="42"></blog-post>
<blog-post v-bind:likes="post.likes"></blog-post>

3.2 Truyền dữ liệu boolean vào component

<blog-post favorited></blog-post>

<base-input v-bind:favorited="false">

<base-input v-bind:favorited="post.currentUserFavorited">

3.3 Truyền mảng vào component

<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>

<blog-post v-bind:comment-ids="post.commentIds"></blog-post>

3.4 Truyền một đối tượng vào component

<blog-post v-bind:comments="{ id: 1, title: 'My Journey with Vue' }"></blog-post>

<blog-post v-bind:post="post"></blog-post>

3.5 Truyền một thuộc tính của một đối tượng vào component

post: {
  id: 1,
  title: 'My Journey with Vue'
}
<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title"
></blog-post>

4. Component bên trong một component khác

Component inside component like lego

Cái hay nhất của component là có thể sử dụng lại trong một component khác, như vậy các ứng dụng chỉ đơn giản là các component được lắp ghép với nhau. Chúng ta cùng xem một ví dụ tiếp theo:

<!DOCTYPE html>
<html>
<head>
    <title>Component trong component - Example 3 - Allaravel.com</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
</head>
<body>
    <template id="hello-template">
        <div class="row">
            <div class="col-md-12">
                <h1>{{ message }} {{ userName }}</h1>
            </div>
        </div>
    </template>
    <template id="form-template">
        <div class="row">
            <div class="col-md-3">
                <label for="name">Tên bạn là gì?</label>
            </div>
            <div class="col-md-9">
                <input class="form-control" v-model="userName" type="text">
            </div>
        </div>
    </template>
    <template id="greeting-template">
        <div>
            <form-component :user-name="userName"></form-component>
            <hello-component :user-name="userName"></hello-component>
        </div>
    </template>
    <div class="container" id="app">
        <greeting-component></hello-component>
    </div>
    <script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
    <script type="text/javascript">
        Vue.component('hello-component',{
            data: function () {
                return {
                    message: 'Xin chào'
                }
            },
            props: ['userName'],
            template: '#hello-template'
        })
        Vue.component('form-component', {
            template: '#form-template',
            props: ['userName']
        });
        Vue.component('greeting-component', {
            template: '#greeting-template',
            data: function () {
                return {
                    userName: 'Allaravel'
                }
            }
        });
        new Vue({
            el: '#app'
        });
    </script>
</body>
</html>

Trong ví dụ này chúng ta có 3 component: hello-component để hiển thị lời chào, form-component để nhập vào tên người dùng và greeting-component sử dụng hai component trước đó tạo thành một component mới. Chúng ta cũng gặp lại cú pháp truyền dữ liệu từ ngoài vào trong component, nó cũng được áp dụng khi truyền dữ liệu từ component cha vào component con.

Mọi thứ hiển thị bình thường nhưng khi bạn thử thay đổi ô nhập liệu tên người dùng, bạn sẽ nhận được một lỗi từ Vue.js:

Lỗi thay đổi giá trị component cha

Đại ý lỗi này là tránh thay đổi giá trị được truyền vào một cách trực tiếp, thay vào đó sử dụng một thuộc tính được khai báo trong data hoặc computed dựa trên giá trị truyền vào. Giá trị bị thay đổi ở đây là userName. Thay đổi lại code trên theo gợi ý core Vue.js đưa ra:

<template id="form-template">
    <div class="row">
        <div class="col-md-3">
            <label for="name">Tên bạn là gì?</label>
        </div>
        <div class="col-md-9">
            <input class="form-control" v-model="name" type="text">
        </div>
    </div>
</template>
Vue.component('form-component', {
    data: function (){
        return {
            name: this.userName
        }
    },
    template: '#form-template',
    props: ['userName']
});

Trong form-component chúng ta không sử dụng trực tiếp thuộc tính userName nữa mà khai báo một thuộc tính khác là name trong data và khởi tạo nó bằng giá trị của userName được truyền vào. Như vậy, khi bạn thay đổi tên người dùng trong ô nhập liệu, nó chỉ tác động đến thuộc tính name cục bộ.

Với lời chào chúng ta muốn sẽ loại bỏ ký tự trống ở hai đầu chuỗi tên người dùng và viết hoa tên người dùng, tạo ra một thuộc tính computed để làm việc này (Tại sao dùng computed, xem lại bài viết Cơ bản về computed trong Vue.js):

<template id="hello-template">
    <div class="row">
        <div class="col-md-12">
            <h1>{{ message }} {{ uperCaseUser }}</h1>
        </div>
    </div>
</template>
Vue.component('hello-component',{
    data: function (){
        return {
            message: 'Xin chào'
        }
    },
    computed: {
        uperCaseUser: function (){
            return this.userName.trim().toUpperCase()
        }
    },
    props: ['userName'],
    template: '#hello-template'
})

Tuy nhiên, code sửa đổi vẫn chưa đáp ứng được yêu cầu, chúng ta muốn khi thay đổi tên người dùng thì tên đó phải được cập nhật vào lời chào.

Mô hình component bên trong component

Tức là phần code trên mới chỉ đáp ứng được luồng dữ liệu truyền dữ liệu từ component cha vào component con (chiều 1), còn chiều ngược (chiều 2) lại hiện chưa thực hiện được do khi truyền từ cha vào con chỉ theo một chiều (one-way data binding).

5. Kết luận

Các khái niệm trong component đã dần được giới thiệu đến các bạn, chúng ta đã đi được nửa chặng đường tìm hiểu về component và tôi luôn muốn bạn nhớ vài điều: component là một code block chứa HTML, Javascript, CSS nó giống như “function” trong ngôn ngữ lập trình thông thường và nó cũng có thể có tham số để truyền dữ liệu vào. Ví dụ về component trong component còn dở dang với chiều ngược lại của đường đi dữ liệu nhưng bài viết cũng khá dài, nghỉ ngơi và nhớ dành thời gian xem bài kết tiếp nhé.

Add Comment