Data flow in ReactJS

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

alt

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

Screen Shot 2015-05-24 at 9.57.51 PM.png

├── 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ệu
  • state: 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ì propsstate 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ào
  • state: bao gồm filterTextinStockOnly, được khởi tạo bởi getInitialState

Và ta có thể thấy rằng cả cả stateprops đều được sử dụng để truyền tham số xuống các component con là SearchBarProductTable. 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à ProductRowProductCategoryRow

ReactJS-searchForm.jpg

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.