React.js mà nhiều người đang nhắc đến, thích hợp cho những ứng dụng Web nào?

Bài viết này trình bày lại nội dung của bài thuyết trình “Introduction to React” được phát biểu vào ngày 21/2/2015 tại Frontend Conference.

File thuyết trình có thể tham khảo theo link dưới đây.

Introduction To React // Speaker Deck

React.js là gì?

React.js là 1 thư viện JavaScript tạo ra bởi Facebook.

http://facebook.github.io/react/

Như khái niệm trên trang web chính thức “A JavaScript library for building user interface”, React.js là một thư viện sinh ra để xây dựng giao diện người dùng (UI). Nó không phải là Framework mà chỉ là thư viện, do đó trong MVC nó sẽ tương ứng với phần V.

Ngoài Facebook và Instagram – nơi làm ra và maintain React.js, Yahoo hay Airbnb là những ví dụ nổi bật có sử dụng thư viện này. Hiện nay, đây là một thư viện thu hút được rất nhiều sự quan tâm.

Đặc tính của React.js

Để hiểu về đặc tính của React.js, trước hết chúng ta hãy cùng nhìn vào nhược điểm của các thư viện khác. Chẳng hạn như thư viện Todo MVC - khi ta gửi một form để thêm một mục vào trong list.

jQuery

Nếu bạn chỉ đơn thuần dùng JQuery thì code sẽ thành ra như sau.

// When submit
$('form').on('submit', function() {
  // Create element
  var $li = $('<li>');
  // ...

  // Add to list
  $('ul').append($li);
});

Nếu như thế này thì data chỉ lưu vào DOM nên nếu bạn muốn thêm chức năng thì sẽ rất vất vả, và bạn phải viết test code cho phần đó nữa.

Backbone.js

Bạn có thể giải quyết vấn đề trên, ở một mức nào đó, bằng việc sử dụng Backbone.js. Khi đó, cấu trúc code sẽ được chia thành Model (quản lí data), View (quản lí hiển thị) và thậm chí trong View sẽ chia thành các component. Ví dụ, nó sẽ thành ra như sau.

var FormView = Backbone.View.extend({
  onSubmit: function() {
    // Only create data and add to list
    this.collection.add(data)
  }
});

var ListView = Backbone.View.extend({
  initialize: function() {
    // Update list when model is updated
    this.collection.on('add', this.render);
  }
});

UI được chia thành các phần con như FormView hay ListView mà ở đây, chúng ta gọi chung là các component.

Với việc chia nhỏ và quản lí data cùng các component như vậy, về mô hình mà nói có thể coi Backbone.js là một công cụ tốt. Tuy nhiên, khi muốn update View bạn sẽ phải viết thêm một cách thủ công, cùng với đó là việc quản lí các event giữa Model và View sẽ rất phức tạp. Do đó, khi chức năng càng mở rộng bạn sẽ càng khó xoay sở. Tất nhiên là bạn có thể dùng wrapper library của Backbone.js như Marionette hay Chaplin nhưng về bản chất, nhược điểm của chúng giống nhau.

Angular.js, Vue.js

Angular.js, Vue.js là những đại diện của thư viện dòng MVVM với đặc điểm : dữ liệu thay đổi thì hiển thị tự động thay đổi theo.

<form ng-submit="onSubmit()">
  <input type="text" ng-model="text">
</form>

<ul>
  <li ng-repeat="item in list">
    {{item.text}}
  </li>
</ul>
// Controller
$scope.onSubmit = function() {
  // View changes when data is updated
  $scope.list.push(newItem);
};

Theo như trên, chúng ta nhập HTML vào, sau đó thay đổi data trên JavaScript thì hiển thị cũng sẽ thay đổi theo. Tuy nhiên, những thư viện này cũng vậy : quy mô càng mở rộng thì quản lí càng khó khăn.

React.js

Backbone.js hay Angular.js, mặc dù quy mô càng to thì quản lí càng khó nhưng nếu chúng ta cấu trúc code tốt thì vẫn có thể làm ra một ứng dụng dễ quản lí.

Tuy nhiên, những thư viện đó vốn dĩ không phải sinh ra để phục vụ cho mục đích như vậy. Thế nên việc cấu trúc code tốt khi sử dụng chúng sẽ không phải là việc dễ dàng.

React.js trái lại, là thư viện phục vụ cho mục đích đó : dễ dàng quản lí khi quy mô mở rộng. Hay ngược lại, nó không dành cho những ứng dụng nhỏ cần hoàn thành sớm. Đối với những ứng dụng đó, trong nhiều trường hợp, Backbone.js hay Vue.js lại dễ dùng hơn.

Đặc tính của React.js là khiến các component luôn ở trạng thái stateless một cách nhiều nhất có thể, khiến ta dễ dàng quản lí chúng.

var Form = React.createClass({
  onSubmit: function() {
    // Inform change to parent component
  },
  render: function() {
    return <form onSubmit={this.onSubmit}>...</form>;
  }
});

var List = React.createClass({
  render: function() {
    // Reform based on data received from parent component
    return <ul>{this.props.list.map(...)</ul>;
  }
});

Các component nhận data từ lớp mẹ và dựa vào đó để xây dựng View. Điểm mấu chốt ở đây là bản thân các component không mang một trạng thái nào. Nó chỉ có việc xuất ra hiển thị dựa vào những đầu vào từ bên ngoài (thường là từ component mẹ). Do đó những component đó sẽ dễ quản lí, dễ test và dễ tái sử dụng.

Tất nhiên là nếu tất cả các component đều stateless thì chẳng khác nào 1 trang HTML tĩnh, vì thế ta cần cả những component có state nữa. Tuy nhiên, quan điểm của React.js là tối giản những component như vậy và xây dựng UI dựa vào những component stateless là chủ yếu.

Theo như biểu đồ hình cây bên dưới, chỉ phần gốc là có trạng thái còn tất cả các nhánh đều không cần.

Screen-Shot-2015-02-28-at-4.03.29-PM-640x442.png

Hơn nữa, khi ta thêm trạng thái cho một component thì cấu trúc của cây cũng sẽ tự động thay đổi. Ta không cần phải tự tay làm. Về điểm này thì nó giống Angular.js – dữ liệu thay đổi thì hiển thị tự động thay đổi theo.

Screen-Shot-2015-02-28-at-3.58.25-PM-640x482.png

Chú thích

  1. User action
  2. Update status
  3. Update to child component

Về cơ bản thì trừ gốc ra, các component khác nên là stateless. Tuy nhiên, khi data phải thay đổi theo các input element hay user action thì khi đó vẫn cần có state.

Virtual DOM

Việc chỉ để cho mỗi phần gốc có trạng thái và tái cấu trúc mọi thứ khi nó thay đổi khiến cho mô hình trở nên đơn giản. Nhưng, việc cả DOM tree phải thay đổi theo một phần nhỏ như vậy sẽ khiến tốc độ xử lí bị ảnh hưởng.

Để giải quyết việc đó React.js sử dụng Virtual DOM.

Virtual DOM, nói 1 cách tổng quát, là 1 object của JavaScript mang trong mình một DOM tree, mỗi khi data thay đổi thì tự tính toán phần chênh lệch của object so với tree thật, nhằm mục đích tối giản việc re-rendering vào tree thật.

Trong React.js ta tạo một component React.createClass, và định nghĩa Virtual DOM trong giá trị trả về của render method. Khi tạo Virtual DOM, ta dùng React.createElement.

var MyComponent = React.createClass({
  render: function() {
    return React.createElement("div", {className: "foo"},
      React.createElement("div", {className: "bar"},
        "Hello ", this.props.name
      )
    );
});

Mặt khác, ta có thể diễn đạt Virtual DOM dưới dạng syntax như XML bằng cách sử dụng JSX – một syntax độc lập.

var MyComponent = React.createClass({
  render: function() {
    return (
      <div className="foo">
        <div className="bar">
          Hello {this.props.name}
        </div>
      </div>
    );
  }
});

Ta có thể dùng react-tools hay babel để biến đổi đoạn code trên thành đoạn code JavaScript có thể build được.

Tốc độ của React.js

Chắc các bạn cũng từng nghe nói là “React.js rất nhanh” nhưng có thật là như vậy không? Với việc ta import Virtual DOM, thay đổi trên DOM sẽ là tối giản kể cả khi ta thay đổi dữ liệu phần gốc nên tốc độ sẽ cao nhưng dù sao, cao cũng chỉ là cao so với trường hợp ta cấu trúc lại toàn bộ DOM mà thôi. Tôi tính toán tốc độ xử lí của React.js trong 3 trường hợp sau.

  1. Sử dụng Backbone.js, chỉ tái cấu trúc những phần trong DOM bị thay đổi.
  2. Sử dụng Backbone.js, tái cấu trúc toàn bộ DOM khi có phần bị thay đổi.
  3. Sử dụng React.js, thông qua Virtual DOM, tái cấu trúc khi có thay đổi dựa trên tính toán phần chênh.

Và kết quả như sau.

Screen-Shot-2015-02-28-at-3.49.24-PM-640x402.png

TodoMVC Benchmark

Theo như kết quả, so với việc dùng Backbone.js để tái cấu trúc từng phần thì React.js chậm hơn, nhưng so với việc dùng Backbone.js để tái cấu trúc toàn bộ DOM thì nhanh hơn nhiều.

Như đã nói ở trên, việc chỉ phần gốc mang trạng thái sẽ khiến mô hình dữ liệu đơn giản đi rất nhiều. Có thể nói React.js vẹn toàn cả ở 2 mặt mô hình và tốc độ.

Flux

Flux là 1 mô hình thiết kế application do Facebook đề xuất. Mô hình này hay được so sánh với MVC.

So với MVC, nó được xem là có đặc tính dữ liệu chỉ lưu thông theo một chiều.

flux-simple-f8-diagram-with-client-action-1300w-640x193.png

Nguồn : http://facebook.github.io/flux/docs/overview.html

Ở trên chúng ta đã nói về mô hình cơ bản của React.js : chỉ phần gốc mang trạng thái và khi có thay đổi sẽ thay đổi trạng thái phần gốc. Tuy nhiên, chúng ta chưa nói đến việc làm thế nào để thông báo thay đổi đó.

Chẳng hạn, một component nào đó trên tree nhận một tác động từ user action và cần thiết phải thay đổi trạng thái. Khi đó, do các component con của React.js là stateless nên thay đổi đó phải được thông báo ra bên ngoài. Cụ thể là chúng ta có thể publish và dùng event handler để thông báo trực tiếp thay đổi đó đến phần gốc như dưới đây.

var Parent = React.createClass({
  handleChange: function(changedData) {
    // When there is update on child component
  },
  render: function() {
    // Child component's event handler setting
    return <Child onChange={this.handleChange}>a</Child>;
  }
});

var Child = React.createClass({
  handleSubmit: function() {
    // Run event handler received from parent component
    this.props.onChange(changedData);
  },
  render: function() {
    // DOM event handler's setting on form
    return <form onSubmit={this.handleSubmit}>...</form>;
  }
});

Tuy nhiên đối với trường hợp component bị tác động ở rất sâu trong hệ thống thì cách làm này sẽ trở nên phiền phức. Để giải quyết điều này, Flux thông báo tới Action một loạt tất cả những thay đổi xảy ra trên View, và thông báo đến cả Store thông qua Dispatcher.

Store là tầng quản lí dữ liệu, tương ứng với M của MVC. Component gốc của View sẽ thường xuyên đọc Store, Store bị thay đổi thì nó cũng sẽ tự động thay đổi theo.

Screen-Shot-2015-02-28-at-4.12.38-PM-640x479.png

Có nhiều cách khác nhau để xây dựng mô hình Flux. Chẳng hạn như nơi xuất phát của nó : facebook/flux, nhưng trong link này chỉ có cách làm đơn giản, ta chỉ có thể tham khảo dispatcher là chính. Ngoài ra còn nhiều nguồn tham khảo khác như :

spoike/refluxjs

jmreidy/fluxy

yoshuawuyts/barracks

BinaryMuse/fluxxor

deloreanjs/delorean

Kết luận

Trong bài này, chúng ta đã nói về ý tưởng và đặc tính của React.js cũng như Flux.

React.js, như ở trên đã bàn, là thư viện thích hợp cho những ứng dụng cần mở rộng, quy mô lớn chứ không dành cho những ứng dụng nhỏ cần hoàn thành sớm. Đối với những ứng dụng nhỏ đó, Angular.js hay Vue.js thường thích hợp hơn, còn đối với những trang tĩnh chỉ có 1 chút UI thì chỉ cần sử dụng jQuery có lẽ cũng đủ rồi.

Khi tự tay dùng React.js và Flux để viết 1 Application bạn sẽ hiểu : việc liên tục phải kết nối những stateless component là khá vất vả. Nhưng sự vất vả đó sẽ được đền đáp bằng 1 ứng dụng rất bền, đẹp và dễ maintain.

Nếu bạn có hứng thú với những thành quả như vậy thì tôi khuyên bạn nên cân nhắc việc dùng React.js.

Nguồn : https://html5experts.jp/hokaccha/13301/

Người dịch : Phan Hoàng Minh