Hướng dẫn Flux qua ví dụ
This post hasn't been updated for 3 years
Giới thiệu về Flux
Flux là một kiến trúc phát triển ứng dụng mà Facebook dùng để xây dựng phần client-side cho những ứng dụng web của họ. Nó giúp làm việc với các components của React một cách dễ dàng bằng cách sử dụng luồng dữ liệu một chiều (Unidirectional Data Flow). Chúng ta cần lưu ý rằng, Flux là một kiến trúc, chứ không phải là một framework.
Tổng quan về kiến trúc của Flux
Những khái niệm chính
- Store: Trung tâm dữ liệu, xử lí hành động liên quan đến dữ liệu, và gửi đi thông báo cho các listener khi dữ liệu thay đổi.
- Dispatcher: Đối tượng trung gian, gửi những hành động (actions) từ view đến stores.
- View: Nhận dữ liệu và các cập nhật từ Store, và gọi các actions.
- Action: Phát tán (dispatch) actions bằng Dispatcher đến những nơi đã đăng kí (register) nhận action đó.
Làm một ứng dụng theo kiến trúc Flux
Những khái niệm này nghe khá trừu tượng và các bài viết về lí thuyết kiến trúc của Flux trên Viblo cũng đã có nhiều, mình cũng không muốn đi quá chi tiết vào chúng nữa. Trong bài viết này, mình sẽ hướng dẫn các bạn tìm hiểu về Flux thông qua việc từng bước xây dựng một ứng dụng CRUD (Create-Read-Update-Delete) đơn giản theo mô hình Flux. Với mình thì việc học qua các ví dụ luôn là dễ hiểu nhất.
Mình sẽ xây dựng một ứng dụng quản lí sinh viên. Cho phép người dùng xem danh sách sinh viên, thêm sinh viên mới, sửa và xoá các sinh viên hiện tại. Mã nguồn các bạn có thể xem tại: https://github.com/hieubm/flux-sample
Đầu tiên, chúng ta sẽ tìm hiểu vể Store bằng cách xây dựng StudentStore
, là nơi lưu trữ thông tin về tất cả sinh viên.
stores/student-store.jsx
var _ = require("underscore"),
EventEmitter = require('events').EventEmitter;
var _students = [{name: 'Nguyen Van A'}, {name: 'Bui Thi B'}];
var StudentStore = _.extend(EventEmitter.prototype, {
getStudents: function() {
return _students;
},
});
Trong đoạn code này, chúng ta tạo một mảng _students
lưu trữ thông tin về sinh viên. Mình có để mặc định dữ liệu ban đầu bao gồm 2 sinh viên "Nguyen Van A" và "Bui Thi B".
Một lưu ý là trong kiến trúc Flux, đối tượng Store chỉ có hàm get chứ không có hàm set, vậy nên trong StudentStore mình chỉ viết hàm getStudents
và trả lại mảng _students
Sau khi có store, chúng ta sẽ cần hiển thị chúng ra.
components/main.jsx
var React = require("react"),
StudentStore = require("../stores/student-store"),
StudentList = require("./student-list");
var Main = React.createClass({
getInitialState: function() {
return {
students: StudentStore.getStudents(),
}
},
render: function() {
return (
<div className="row">
<h1 className="text-center">Student Management</h1>
<div className="col-md-4 col-md-offset-4">
<StudentList students={this.state.students} />
</div>
</div>
);
}
});
module.exports = Main;
components/student-list.jsx
var React = require("react"),
var StudentList = React.createClass({
render: function() {
var studentList = this.props.students.map(function(student, index) {
return (
<tr key={index}>
<td>{student.name}</td>
</tr>
);
});
return (
<div>
<table className="table">
<tbody>
{studentList}
</tbody>
</table>
</div>
);
}
});
module.exports = StudentList;
Trong main.jsx
chúng ta lấy ra danh sách sinh viên từ store và gán vào state trong hàm getInitialState
. Sau đó truyền danh sách này vào trong component StudentList
, component này có nhiệm vụ hiển thị toàn bộ sinh viên ra. Và đây là kết quả:
Tiếp đến, để thêm sinh viên mới, chúng ta sẽ tạo component StudentForm
có nhiệm vụ làm form nhập liệu để add student.
var React = require("react"),
StudentActions = require("../actions/student-actions");
var StudentForm = React.createClass({
_onClickAdd: function() {
StudentActions.addStudent({name: this.state.name});
},
_onChangeName: function(e) {
this.setState({
name: e.target.value,
});
},
getInitialState: function() {
return {
name: "",
}
},
render: function() {
return (
<div className="row" style={{margin: "10px"}}>
<div className="col-md-2">
Name:
</div>
<div className="col-md-6">
<input value={this.state.name} onChange={this._onChangeName} />
</div>
<div className="col-md-2">
<input type="button" value="Add" className="btn btn-primary" onClick={this._onClickAdd} />
</div>
</div>
);
}
});
module.exports = StudentForm;
Như các bạn thấy, trong file này, chúng ta có một text-field Name và một button Add. Mỗi khi người dùng nhập liệu vào trong text-field, thì this.state.name
sẽ được cập nhật giá trị. Khi người dùng ấn Add, hàm _onClickAdd
sẽ được gọi và hàm này gọi đến một action với tham số là tên của sinh viên đã nhập StudentActions.addStudent({name: this.state.name});
Tiếp đến, chúng ta sẽ xây dựng file actions/student-action.jsx
var StudentConstants = require("../constants/student-constants"),
AppDispatcher = require("../dispatcher/app-dispatcher");
var StudentActions = {
addStudent: function(student) {
AppDispatcher.dispatch({
action: StudentConstants.ACTION_ADD,
student: student,
})
},
}
module.exports = StudentActions;
Actions trong Flux làm nhiệm vụ chính là phát tán (dispatch) thông tin về action này đến tất cả những nơi đã đăng kí (register) nhận thông tin về action đó. Việc phát tán thông qua hàm dispatch
của một đối tượng đặc biệt là Dispatcher. Để sử dụng Dispatcher này, chúng ta cần khởi tạo nó trong file dispatcher/app-dispatcher.jsx
var Dispatcher = require('flux').Dispatcher;
module.exports = new Dispatcher();
Hàm dispatch
nhận vào một đối tượng bất kì. Trong ví dụ này, chúng ta truyền vào một đối tượng có 2 thuộc tính. Thứ nhất là tên action và thứ hai là đối tượng student cần add. Tên action chúng ta sử dụng hằng số ACTION_ADD đặt trong actions/student-actions.jsx
.
module.exports = {
ACTION_ADD: "ACTION_ADD",
}
Bây giờ chúng ta cần chỉnh sửa một chút trong student-store.jsx
// ...
var CHANGE_EVENT = 'change';
var _students = [{name: 'Nguyen Van A'}, {name: 'Bui Thi B'}];
function _addStudent(student) {
_students.push(student);
}
var StudentStore = _.extend(EventEmitter.prototype, {
getStudents: function() {
return _students;
},
emitChange: function() {
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
}
});
AppDispatcher.register(function(payload) {
switch (payload.action) {
case StudentConstants.ACTION_ADD:
_addStudent(payload.student);
StudentStore.emitChange();
break;
}
});
// ...
Chúng ta hãy để ý đến đoạn AppDispatcher.register
. Đây là nơi đăng kí nhận các event do các actions gửi đi thông qua hàm dispatch. Ở đây, chúng ta chỉ nhận xử lí action ACTION_ADD
và gọi đến hàm nội bộ _addStudent
. Hàm _addStudent
sẽ push đối tượng student mới vào trong mảng _students
. Sau khi thêm thành công, chúng ta cần thông báo cho View biết về sự thay đổi này thông qua hàm StudentStore.emitChange()
. Hàm này sẽ broadcast CHANGE_EVENT
đến toàn bộ các đối tượng đã đăng kí nhận thông tin thay đổi bằng hàm addChangeListener
. Bây giờ chúng ta sẽ quay lại main.jsx
để đăng kí nhận các thay đổi.
//...
var Main = React.createClass({
componentDidMount: function() {
StudentStore.addChangeListener(this._onChange);
},
_onChange: function() {
this.setState({
students: StudentStore.getStudents(),
})
},
//...
Đoạn code này nghĩa là, sau khi các component được mount, chúng ta đăng kí nhận các thông tin cập nhật mới nhất về dữ liệu students. Khi dữ liệu về students có thay đổi, hàm _onChange
sẽ được gọi, và cập nhật lại state của Main component.
Chúng ta hãy chạy thử chương trình, và thử thêm một sinh viên mới, và đây là kết quả:
Đến bước này thì chúng ta đã nắm khá rõ về chức năng và cách hoạt động của các thành phần View, Action, Store, Dispatcher trong Flux rồi. Tương tự như vậy, chúng ta sẽ làm tiếp tính năng xoá sinh viên. Ý tưởng là bên cạnh mỗi sinh viên sẽ có một button Remove, khi click vào thì sinh viên đó sẽ bị xoá khỏi danh sách. Để thực hiện, chúng ta quay lại với student-list.jsx
var React = require("react"),
StudentActions = require("../actions/student-actions");
var StudentList = React.createClass({
render: function() {
var studentList = this.props.students.map(function(student, index) {
return (
<tr key={index}>
<td>{student.name}</td>
<td className="col-md-1">
<input type="button" value="Remove" className="btn btn-danger"
onClick={StudentActions.removeStudent.bind(null, index)} />
</td>
</tr>
);
}.bind(this));
//...
Với mỗi student, chúng ta thêm một button Remove, khi click vào chúng ta gọi đến StudentActions.removeStudent
với tham số là index của sinh viên đó trong mảng.
Tiếp đến chúng ta cần thêm phần xử lí cho removeStudent
tương tự như cách chúng ta đã làm addStudent
actions/student-actions.jsx
//...
removeStudent: function(index) {
AppDispatcher.dispatch({
action: StudentConstants.ACTION_REMOVE,
index: index,
})
}
//...
constants/student-constants.jsx
module.exports = {
ACTION_ADD: "ACTION_ADD",
ACTION_REMOVE: "ACTION_REMOVE",
}
stores/student-store.jsx
//...
function _removeStudent(index) {
_students.splice(index, 1);
}
//...
AppDispatcher.register(function(payload) {
switch (payload.action) {
//...
case StudentConstants.ACTION_REMOVE:
_removeStudent(payload.index);
StudentStore.emitChange();
break;
}
});
//...
Đây là kết quả:
Tương tự như vậy, chúng ta có thể xây dựng tiếp phần sửa thông tin cho sinh viên. Bạn có thể xem mã nguồn đầy đủ tại trang Github: https://github.com/hieubm/flux-sample
Tham khảo
https://viblo.asia/thangtd90/posts/NznmMd34Rr69 https://viblo.asia/tungshooter/posts/jdWrvwjzGw38 http://blog.andrewray.me/reactjs-for-stupid-people/ http://blog.mimacom.com/introduction-to-react-and-flux/ http://facebook.github.io/flux/docs/overview.html
All Rights Reserved