Data flow in ReactJS
Bài đăng này đã không được cập nhật trong 3 năm
Mặc dù chỉ là một Framework thuần view nhưng ReactJS đã tạo được sự chú ý nhất định trong làng công nghệ bởi hiệu năng tuyệt vời của mình. Điều làm ReactJS trở nên khác biệt so với các Front-end Framework khác chính là kiến trúc luồng dữ liệu một chiều (One-way data flow). Trong bài viết này sẽ đi sâu phân tích các đặc điểm cơ bản cũng như cách thức luồng dữ liệu di chuyển trong ReactJS
Để minh hoạ cho bài viết này, chúng ta sẽ sử dụng ví dụ đã được đưa ra trên trang chủ của ReactJS, một form search động sử dụng ReactJS
Source code của phần ví dụ này có thể clone trực tiếp từ trên github
$ git clone https://github.com/ducthien1490/reactJS-sample.git
$ git checkout master
$ ruby server.rb
Sau đó chúng ta chỉ cần truy cập trực tiếp trên trình duyệt bởi địa chỉ http://localhost:3000/ để có thể chạy thử
Thành phần cơ bản của ReactJS
Khác với các Framework khác như AngularJS hay EmberJS, ReactJS không có những module chuyên dụng để xử lý data. Do đó nhiều người tham chiếu ReactJS như phần View của một MVC framework, tuy nhiên thay vì sử dụng phần view thuần HTML, ReactJS chia nhỏ view thành các component nhỏ có mỗi quan hệ chặt chẽ với nhau Trong ví dụ trên, ta có thể thấy search table của chúng ta có thể chia nhỏ như sau
├── FilterableProductTable
│ ├── SearchBar
│ └── ProductTable
│ ├── ProductCategoryRow
│ └── ProductRow
Tại sao chúng ta phải quan tâm tới cấu trúc và mối quan hệ giữa các component trong ReactJS? Câu trả lời chính là luồng truyền dữ liệu trong ReactJS: Luồng dữ liệu một chiều từ cha xuống con
Tuy nhiên, sẽ có câu hỏi đặt ra là: dữ liệu sẽ được truyền dựa trên phương tiện nào?
=> câu trả lời sẽ là
Props and State
Props và State là hai tham số cơ bản của ReactJS đều được sử dụng để truyền tải dữ liệu. Điểm khác biệt cơ bản nhất của hai tham số này là:
props
: Đây là tham số mà bản thân component không thể tự sinh ra, được truyền từ bên ngoài vào. Điểm đặc biệt làprops
không thể thay đổi được trong quá trình truyền tải dữ liệustate
: Tham số này được sinh ra nội tại bên trong component, đặc trưng cho component đó. Có khả năng thay đổi được giá trị
Tuy nhiên, nếu để sử dụng truyền tải dữ liệu xuống các component lớp dưới thì props
và state
không có sự khác biệt.
var FilterableProductTable = React.createClass({
getInitialState: function() {
return {
filterText: '',
inStockOnly: false
};
},
render: function() {
return (
<div>
<SearchBar
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
/>
<ProductTable
products={this.props.products}
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
/>
</div>
);
}
});
React.render(<FilterableProductTable products={PRODUCTS} />,
document.getElementById('products'));
Trên đây là đoạn code dùng để khởi tạo cho khung search với tên component là FilterableProductTable
. Ở component này:
props
: chỉ có products được truyền từ bên ngoài vàostate
: bao gồmfilterText
vàinStockOnly
, được khởi tạo bởigetInitialState
Và ta có thể thấy rằng cả cả state
và props
đều được sử dụng để truyền tham số xuống các component con là SearchBar
và ProductTable
. Và cứ liên tiếp tuần tự truyền như vậy, dữ liệu sẽ được truyền tải xuống tới component ở level thấp nhất là ProductRow
và ProductCategoryRow
Event handling
Ở trong form search này, chúng ta có hai thành phần thay đổi động theo input đầu vào là text box mang filterText
và chekcbox để kiểm tra inStockOnly
. Khi thông tin input thay đổi, chúng ta sẽ hiển thị product dựa trên thông tin đó.
Chúng ta hãy thử thực hiện yêu cầu này khi không sử dụng Framework nào cả (hỗ trợ bởi jQuery), sử dụng AngularJS và cuối cùng là ReactJS
No Framework
$('input#filterText, input#isStockOnly').change(function(){
var filterText = this.id === 'filterText' : $(this).val() ? '';
var isStockOnly = this.id === 'isStockOnly' : $(this).val() ? false;
var filterProduct = function(filterText, isStockOnly){
//implement filter product based on filterText and isStockOnly
}
});
Ở đây, chúng ta sẽ lấy giá trị trực tiếp từ SearchBar
rồi sử dụng hàm filterProduct
để tác động tực tiếp xuống ProductTable
. Chúng ta có thể dễ dàng nhận ra rằng, SearchBar
có thể trực tiếp tương tác với ProductTable
để thay đổi thông tin hiển thị và không ảnh hưởng gì tới parent element ở dây là FilterProductTable
AngularJS
angular.module('app',[]).directive('searchTable', ['$scope', function($scope){
$scope.filterText = '';
$scope.isStockOnly = '';
$scope.handleChange = function(){
$scope.filterText = this.inputText.value;
$scope.isStockOnly = this.checkbox.value;
loadProductTable($scope.filterText, $scope.isStockOnly);
}
loadProductTable = function(filterText, isStockOnly){
//changing product list of table
};
}]);
Ta có thể nhận thấy là về cơ chế cũng không có khác biệt quá nhiều so với không sử dụng framework, tuy nhiên việc thay đổi product trong table có một chút đơn giản hơn khi chúng ta sử dụng biến $scope để gán data. Có thể nói rằng ở AngularJs là luồng dữ liệu 2 chiều (two-way data flow) vì không có sự phân biệt giữa các thành phần cấu thành và thông qua biến $scope, các element có thể dễ dàng tương tác với phần còn lại
ReactJS
Như đã trình bày ở phần trên, chúng ta biết rằng event hanlding của ReactJS phải tuân theo cấu trúc dữ liệu một chiều từ cha xuống con. Do đó, việc các thành phần trong SearchBar
tương tác trực tiếp với ProductTable
hay là parent element là filterableProductTable
là điều không thể vì nó sẽ đi ngược lại với one-way data flow.
Để có thể thực hiện điều này trong ReactJS, chúng ta phải thay đổi state
của component gốc chính là filterableProductTable
var SearchBar = React.createClass({
handleChange: function() {
this.props.onUserInput(
this.refs.filterTextInput.getDOMNode().value,
this.refs.inStockOnlyInput.getDOMNode().checked
);
},
render: function() {
return (
<form>
<input
type="text"
placeholder="Search..."
value={this.props.filterText}
ref="filterTextInput"
onChange={this.handleChange}
/>
<p>
<input
type="checkbox"
checked={this.props.inStockOnly}
ref="inStockOnlyInput"
onChange={this.handleChange}
/>
{' '}
Only show products in stock
</p>
</form>
);
}
});
var FilterableProductTable = React.createClass({
getInitialState: function() {
return {
filterText: '',
inStockOnly: false
};
},
handleUserInput: function(filterText, inStockOnly) {
this.setState({
filterText: filterText,
inStockOnly: inStockOnly
});
},
render: function() {
return (
<div>
<SearchBar
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
onUserInput={this.handleUserInput}
/>
<ProductTable
products={this.props.products}
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
/>
</div>
);
}
});
Điều đặc biệt ở đây là chúng ta biến hàm setState()
của element gốc thành props
của SearchBar
và từ SearchBar
chúng ta có thể thay đổi trực tiếp state
của FilterableProductTable
và sẽ tự động thay đổi tên product hiển thị.
Việc thay đổi này tuy có phần hơi phức tạp so với các phương pháp ở trên nhưng có thể mang lại một số lợi ích như:
- Đảm bảo sự xuyên suốt từ trên xuống dưới
- Hạn chế thay đổi trực tiếp trên DOM
- Quản lý các event bên trong từng component
- Có thể sử dụng lại trong tương lai (reuseable)
Kêt luận
Việc ReactJS sử dụng one-way data flow có thể gây ra một chút khó khăn cho những người muốn tìm hiểu và ứng dụng vào trong các dự án. Tuy nhiên, cơ chế này sẽ phát huy được ưu điểm của mình khi cấu trúc cũng như chức năng của view trở nên phức tạp thì ReactJS sẽ phát huy được vai trò của mình.
All rights reserved