Dù ở bất cứ hoàn cảnh nào, việc “giao thương” qua lại là không thể tránh khỏi. Trong ứng dụng Vue.js cũng vậy. Có rất nhiều cách khác nhau để chia sẻ dữ liệu giữa các components. Tùy vào từng tình huống mà giải pháp chia sẻ dữ liệu mà bạn chọn là tốt nhất.
Chia sẻ dữ liệu giữa các components là một tính năng cốt lõi của Vue.js. Nó cho phép bạn module hóa, kiểm soát phạm vi dữ liệu và tạo một luồng trao đổi dữ liệu an toàn trong ứng dụng.
Qua bài viết này, mình sẽ chia sẻ 3 cách để truyền dữ liệu giữa các Components:
- Sử dụng props để truyền dữ liệu từ component cha xuống component con.
- Emitting event để chia sẻ ngược lại từ component con lên component cha.
- Sử dụng Vuex để chia sẻ dữ liệu cho toàn ứng dụng.
Okay, bắt đầu thôi!
Nội dung chính của bài viết
1. Sử dụng Props để truyền dữ liệu từ component cha xuống component con
VueJS props là cách đơn giản nhất để truyền dữ liệu giữa các Components. Hiểu đơn giản props là thuộc tính do chính chúng ta tạo ra (khác với thuộc tính do nhà phát hành vue tạo sẵn).
Sau đó, trong template, chúng ta cung cấp giá trị thông qua thuộc tính tùy chỉnh đó. Như vậy là bạn đã thực hiện chia sẻ dữ liệu giữa các component rồi đấy.
Để dễ hình dung hơn, mình sẽ lấy một ví dụ thế này: Chúng ta có một trang Profile người dùng, trong đó có thông tin về tài khoản của người đó. Với yêu cầu này, mình sẽ tạo 2 components: AccountInfo.vue
và ProfilePage.vue
.
Trong AccountInfo.vue
, mình sẽ khai báo props, trong đó có thuộc tính username
. Kiểu như bên dưới:
//AccountInfo.vue <template> <div id='account-info'> {{ userName }} </div> </template> <script> export default { props: { userName: String } } </script>
Sau đó, để truyền dữ liệu từ component cha (ProfilePage.vue
), chúng ta truyền dữ liệu kiểu như sau:
// ProfilePage.vue <account-info userName='matt' />
Trong Vue.js có các VueJS directives khá hay ho. Ví dụ, bạn có thể sử dụng v:bind để truyền động giá trị cho props.
Ở trong ví dụ trên, giả sử mình muốn gán trị cho userName props thông qua một biến. Chúng ta có thể thực hiện điều này bằng cách sử dụng v:bind
.
<template> <div> <account-info :username="user.username" /> </div> </template> <script> import AccountInfo from "@/components/AccountInfo.vue"; export default { components: { AccountInfo }, data() { return { user: { username: 'matt' } } } } </script>
Nhân tiện đây, mình có một vài lưu ý về cách sử dụng Props trong Vue.js
#Luôn luôn verify các thuộc tính khi định nghĩa Props
Nếu bạn đang trăn trở về cách viết mã nguồn Vue sao cho sạch đẹp hơn, hãy nhớ tới một quy tắc quan trọng: luôn verify props.
Nói một cách ngắn gọn, tức là bạn luôn cần phải định nghĩa rõ ràng kiểu dữ liệu cho props. Nếu sau này khi sử dụng props đó mà bạn truyền sai kiểu dữ liệu, Vue sẽ cảnh báo.
Không nên // Như thế này chỉ OK khi viết prototype props: [' userName '] Nên props: { userName: String }
Verifying props là công việc cần thiết khi xây dựng hệ thống lớn, hoặc thiết kế plugin cho ứng dụng khác. Điều này đảm bảo tất cả mọi người đều phải tuân theo quy tắc mà nhà thiết kế đã quy định.
#Đặt tên cho props theo chuẩn camelCase
Theo như hướng dẫn của chính nhà phát hành Vue, họ khuyến khích đặt tên Component, props theo chuẩn camelCase. Lý do đơn giản là trong Javascript, camelcase là quy ước đặt tên tiêu chuẩn.
// Nên <account-info :my-username="user.username" /> props: { myUsername: String } // Không nên <account-info :myUsername="user.username" /> props: { "my-username": String }
2. Emitting Events để chia sẻ ngược lại từ component con lên component cha.
Với cách sử dụng props, bạn có thể truyền dữ liệu từ cha cho con. Thế component con muốn “biếu” lại gì đó cho component cha thì sao?
Với trường hợp này, chúng ta không thể sử dụng props được. Tất nhiên là Vue có giải pháp thay thế rồi. Đó chính là sử dụng custom events và listeners.
Trong mỗi Vue instance, bạn đều có thể sử dụng hàm .$emit(eventName) để trigger một event. Và chúng ta sẽ tận dụng kỹ thuật này để truyền dữ liệu.
#Tạo một custom event
Quay lại ví dụ về trang profile trên, chúng ta cần một button để thay đổi username
. Khi nhấn vào button này, chúng ta sẽ phát một event: changeUsername
<template> <div id='account-info'> <button @click='changeUsername()'>Change Username</button> {{username}} </div> </template> <script> export default { props: { username: String }, methods: { changeUsername() { this.$emit('changeUsername') } } } </script>
Bên trong component cha (ProfilePage.vue
), chúng ta sẽ bắt event đó và thay đổi giá trị của biến user.username
.
<template> <div> <account-info :username="user.username" @changeUsername="user.username = 'new name'"/> </div> </template>
🔥 Đọc thêm: Hiểu Vuejs Router qua thực hành một dự án
3. Sử dụng Vuex
Qua 2 giải pháp trên, chúng ta đã biết cách chia sẻ dữ liệu giữa các component “trong cùng một gia đình, giữa cha và con”… Thế còn “người ngoài” thì sao? Chúng ta có phải tạo ra một hệ thống phân cấp thật phức tạp, bao phủ toàn bộ ứng dụng để truyền dữ liệu?
Rất may không cần. Thư viện quản lý state Vuex sẽ đơn giản hóa việc truyền dữ liệu. Vuex sẽ tạo một kho lưu trữ dữ liệu mà bất kì component nào cũng có thể lấy và sử dụng được. Kho lưu trữ này được gọi là store.
Vuex là gì
Vuex là thư viện được tách ra từ Vue Core. Mục đích của thư viện Vuex là giúp quản lý state của các component trong Vue.js. Về một khía cạnh nào đó, Vuex khá tương đồng với Redux trong ReactJS. Nguyên tắc của Vuex là state chỉ có thể được thay đổi theo kiểu có thể dự đoán được.
Thư viện vuex không được tích hợp mặc định trong Vue, bạn muốn sử dụng nó thì cần phải cài đặt.
npm install vuex --save
Sau đó, trong thư mục src, bạn tạo thêm thư mục store. Tiếp tục tạo tệp index.js
trong thư mục store.
// src/store/index.js import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); export default new Vuex.Store({ state: {}, getters: {}, mutations: {}, actions: {} });
Tiếp theo, bạn cần khai báo store với Vue instance gốc.
// main.js import store from "./store"; new Vue({ store, ...
Để các bạn có thể hiểu rõ hơn về Vuex, cũng như cách sử dụng, chúng ta sẽ cùng tìm hiểu 4 thành phần chính của Vuex store: State, Getters, Mutations, Actions.
#State
Vuex state là một object chứa dữ liệu được chia sẻ cho toàn ứng dụng. Tất cả Vue instances đều có thể truy cập được dữ liệu này.
Ví dụ:
export default new Vuex.Store({ state: { user: { username: 'matt', fullName: 'Matt Maribojoc' } }, getters: {}, mutations: {}, actions: {} });
Và ở bất kỳ component nào cũng truy cập được, kiểu như thế này:
mounted () { console.log(this.$store.state.user.username); },
#Getters
Chúng ta sử dụng Vuex getters để trả về dữ liệu đã được thay đổi từ giá trị khai báo trong state data. Một case sử dụng phổ biến nhất đó là coi getter như là các thuộc tính đã được tính toán theo mục đích nào đó.
Như trong ví dụ trên, mình muốn lấy first name từ full name của người dùng.
getters: { firstName: state => { return state.user.fullName.split(' ')[0] } }
Sau đó thì chúng ta có thể sử dụng thuộc tính getter này như bình thường.
mounted () { console.log(this.$store.getters.firstName); }
Mặc định Vuex getters chấp nhận 2 tham số:
- state: là state object của ứng dụng.
- getters: là store.getters object – tức là chúng ta có thể gọi getter của một store khác.
Mỗi getter mặc định sẽ có tham số đầu tiên (state). Còn tùy vào thiết kế, yêu cầu bài toán cụ thể mà bạn có thể cần tới tham số thứ 2 (getters).
Ok, lại lấy ví dụ trên, mình tạo getter lấy last name từ full name và first name.
lastName (state, getters) { return state.user.fullName.replace(getters.firstName, ''); }
#Mutations
Mutations là cách duy nhất để thay đổi giá trị của state object. Một chi tiết quan trọng của mutations đó là Mutations phải là synchronous.
Giống như getters, Mutations cũng chấp nhận 2 tham số:
- State: là state object của ứng dụng.
- Payload: là giá trị bạn muốn truyền vào cho mutations
Ví dụ:
mutations: { changeName (state, payload) { state.user.fullName = payload } },
Để gọi Mutations trong component bất kỳ, chúng ta sử dụng hàm commit.
this.$store.commit("changeName", "New Name");
#Actions
Trong Vuex, các actions có cách hoạt động khá giống với Mutation, bởi vì chúng đều được sử dụng để thay đổi state. Tuy nhiên, actions không trực tiếp thay đổi giá trị của state. Thay vào đó, actions sẽ commit các mutations.
Ngoài ra, trong khi mutations phải là hàm đồng bộ, actions thì không bắt buộc như vậy.
Trong hầu hết các trường hợp thì Vuex chấp nhập state là tham số chính, actions còn chấp nhận Context làm tham số. Với Context, chúng cho phép truy cập vào các thuộc tính của Vuex store (ví dụ: state, commit, getters)
Dưới đây là ví dụ về actions: đợi 2 giây rồi commit một changeName mutation.
actions: { changeName (context, payload) { setTimeout(() => { context.commit("changeName", payload); }, 2000); } }
Tạm kết
Qua bài viết này, mình đã giới thiệu 3 cách để bạn có truyền dữ liệu giữa các Components: props, custom events, và Vuex store Tùy vào hoàn cảnh, thiết kế của ứng dụng mà bạn chọn cách làm phù hợp. Cũng có thể trong ứng dụng, bạn phải sử dụng cả 3 cách làm này.
Mình hi vọng, các bạn sẽ hiểu thêm về Vue, về Vuex. Hãy để lại bình luận của bạn ở bên dưới nhé. Mình luôn luôn chờ ý kiến của bạn.
Đọc thêm:
cảm ơn ad nhiều ạ