Compare the special features between React and other frameworks

*Bài viết này được dịch từ 1 bài viết bằng tiếng Nhật của tác giả Yoshioka Tsuneo. Các bạn có thể xem bài viết gốc tại đây.

React, React.js hay ReactJS là một thư viện JavaScript được Facebook phát triển dành riêng cho việc xây dựng giao diện người dùng (UI). Mới xuất hiện chưa lâu nhưng React đã nhận được sự quan tâm từ đông đảo cộng đồng phát triển web và nhanh chóng trở thành một hiện tượng. Tuy nhiên React cũng chỉ là một trong những thư viện hay framework JavaScript xuất hiện ngày càng nhiều trong thời gian gần đây. Chúng ta vừa có React.js, Ractive.js, Aurelia.js và sắp tới sẽ là Angular2. Tất nhiên mỗi thư viện hay framework đều sẽ có những đặc trưng riêng, nhưng đâu là điểm khác nhau giữa chúng? Giữa rất nhiều những sự lựa chọn như thế chúng ta nên bắt đầu từ đâu?

Trong bài viết này tôi sẽ thử so sánh 9 framework phổ biến hiện nay thông qua việc viết những đoạn code cơ bản của từng framework để tạo ra 1 ứng dụng demo cũng cơ bản không kém 😄

Nhìn chung một thư viện hay framework JavaScript thường có những tính năng sau đây:

  • MVC/MVVM model
  • Data binding giữa HTML và JavaScript
  • HTML template
  • URL routing
  • RESTful API
  • Custom directive
  • DI (Dependency Injection)

Tuy không phải tất cả 9 framework đã kể ở trên đều có đầy đủ những tính năng này, nhưng có một tính năng quan trọng không thể thiếu đó là data binding giữa HTML và JavaScript. Đây cũng là tính năng chính mà bài viết này muốn so sánh.

Sample Code

Ứng dụng web demo của chúng ta có 1 form gồm 2 trường input First name và Last name, và 1 trường output hiển thị Full name. Yêu cầu ở đây là khi người dùng thay đổi First name hoặc Last name thì Full name cũng phải được cập nhật ngay.

Tuy chỉ là 1 ứng dụng đơn giản những thông qua đó chúng ta có thể thấy được những điểm mấu chốt trong cơ chế hoạt động của từng framework, ví dụ như

  • Làm thế nào để thể hiện các biến, controller, model trong cả HTML và JavaScript?
  • Làm thế nào để truyền giá trị nhập vào từ HTML form đến model trong JavaScript?
  • Làm thế nào để cập nhật những thay đổi của model trong JavaScript đến HTML?

So sánh các framework

0. jQuery

Trước khi đề cập đến các framework kể trên, chúng ta hãy thử viết ứng dụng trên mà không dùng framework nào.

HTML

First Name: <input id="firstName"/><br>
Last Name: <input id="lastName"/><br>
Full Name: <span id="fullName"></span><br>

JavaScript

function updateFullName(){
    var fullName = $("#firstName").val() + " " + $("#lastName").val();
    $("#fullName").text(fullName);
}
$("#firstName, #lastName").bind("change keyup", function(){
    updateFullName();
});
$("#firstName").val("Taro");
$("#lastName").val("Yamada");
updateFullName();

Demo: http://jsfiddle.net/yoshiokatsuneo/4va165n5/

Chúng ta đều biết là jQuery thừa sức giải quyết nhiệm vụ đơn giản này. Tuy nhiên có 1 số vấn đề với đoạn code. Đầu tiên là dữ liệu không được biểu diễn bằng các biến của JavaScript mà lại thông qua các string dùng trong jQuery selector như #firstName hay #lastName. Các jQuery object này gắn chặt với DOM và hoạt động như những biến global. Một vấn đề nữa là dữ liệu chỉ tồn tại trong HTML (DOM) và không có cấu trúc.

1. Backbone.js

<div id="person">
    First Name: <input id="firstName" value=""><br>
    Last Name: <input id="lastName" value=""><br>
    Full Name: <span id="fullName"></span>
</div>

JavaScript

Person = Backbone.Model.extend({});
PersonView = Backbone.View.extend({
    el: '#person',
    events: {
        'change': 'change',
    },
    initialize: function(){
        this.listenTo(this.model, 'change', this.render);
        this.render();
    },
    change: function(){
        var firstName = $('#firstName').val();
        var lastName = $('#lastName').val();
        this.model.set({firstName: firstName, lastName: lastName});
    },
    render: function(){
        this.$('#firstName').val(this.model.get('firstName'));
        this.$('#lastName').val(this.model.get('lastName'));
        var fullName = this.model.get('firstName')
                        + ' ' + this.model.get('lastName');
        this.$('#fullName').text(fullName);
    },
});
person = new Person({lastName: "Yamada", firstName: "Taro"});
personView = new PersonView({model: person});

Demo: http://jsfiddle.net/yoshiokatsuneo/5u9czbwe/

Backbone.js là 1 framework đơn giản mà phần nhân là Model class và View class. Bằng việc triển khai mô hình MVP (1 biến thể của mô hình MVC), Backbone.js đã có thể loại bỏ vấn đề biến global như trong jQuery, đồng thời có thể cấu trúc lại dữ liệu dưới dạng model, dễ dàng tiến hành module hóa ngay cả với những ứng dụng mới quy mô lớn.

Việc hiển trị trên HTML được xử lý bởi các View class. Mỗi view sẽ có 1 model tương ứng nhưng model và HTML là hoàn toàn tách biệt. HTML và View class giao tiếp với nhau thông qua các hàm val(), text() của jQuery hoặc qua việc theo dõi các event. View class và Model class giao tiếp với nhau thông qua các hàm set, get hoặc qua việc theo dõi các event (listenTo()).

Thực ra ở đây không hề có data binding 1 cách tự động, thay vào đó chúng ta phải viết các hàm để liên kết dữ liệu giữa HTML và JavaScript.

2. Ember.js

HTML

<script type="text/x-handlebars" data-template-name="index">
First Name:{{input type="text" value=firstName}}<br/>
Last Name:{{input type="text" value=lastName}}<br/>
Full Name: {{model.fullName}}<br/>
</script>

JavaScript

App = Ember.Application.create();
App.Person = Ember.Object.extend({
  firstName: null,
  lastName: null,

  fullName: function() {
    return this.get('firstName') + ' ' + this.get('lastName');
  }.property('firstName', 'lastName')
});

var person = App.Person.create({
  firstName: "Taro",
  lastName:  "Yamada"
});

App.Router.map(function () {
});

App.IndexRoute = Ember.Route.extend({
    model:function () {
        return person;
    }
});

Demo: http://jsfiddle.net/yoshiokatsuneo/gLkq1sd5/

Không giống như Backbone.js, Ember.js hỗ trợ data binding 1 cách tự động. Dữ liệu ở HTML và JavaScript sẽ tự cập nhật khi phía kia có thay đổi.

Nói cụ thể hơn thì Ember.js đã giới thiệu cú pháp {{}} cho phép chúng ta liên kết các biến của HTML với các biến của Controller hay Model class. Những thay đổi của các biến đầu vào trên HTML (firstName hay lastName) sẽ được tự động phản ánh lên model (làm thay đổi model). Ngược lại, khi các biến của model có thay đổi, những thay đổi này sẽ được tự động cập nhật lên HTML. Như vậy là thông qua data binding, chúng ta không cần phải viết những hàm chuyên biệt để xử lí các event.

3 Knockout.js

HTML

<p>First name:<input data-bind="value: firstName" /></p>
<p>Last name:<input data-bind="value: lastName" /></p>
<p>Full name:<span data-bind="text: fullName"></span></p>

JavaScript

function AppViewModel() {
    this.firstName = ko.observable("Taro");
    this.lastName = ko.observable("Yamada");

    this.fullName = ko.computed(function() {
        return this.firstName() + " " + this.lastName();
    }, this);
}

// Activates knockout.js
ko.applyBindings(new AppViewModel());

Demo: http://jsfiddle.net/yoshiokatsuneo/3q880ohq/

Cũng giống như Ember.js, Knockout.js cũng hỗ trợ data binding giữa HTML và JavaScript bằng cú pháp khá đơn giản. Ở HTML, những thành phần được khai báo thuộc tính như data-bind="value: firstName" thì dữ liệu sẽ được liên kết với thuộc tính tương ứng trong ViewModel. ViewModel sẽ theo dõi sự thay đổi của những thuộc tính được đánh dấu là observable. Khi có sự thay đổi, những thuộc tính được đánh dấu là computed sẽ được cập nhật 1 cách tự động và thể hiện lên HTML.

4 AngularJS (1.x)

HTML

<div ng-app ng-controller="PersonController">
First Name: <input type=text ng-model="firstName"> <br>
Last Name: <input type=text ng-model="lastName"><br>
Full Name: {{getFullName()}}
</div>

JavaScript

function PersonController($scope) {
  $scope.firstName = "Taro";
  $scope.lastName  = "Yamada";
  $scope.getFullName = function() {
    return $scope.firstName + " " + $scope.lastName;
   };
}

Demo: http://jsfiddle.net/yoshiokatsuneo/pqku2r33/

AngularJS là 1 framework mạnh mẽ được phát triển bởi Google, nó có nhiều tính năg như data binding 2 chiều, routing, RESTful API, DI...

Tuy Ember.js、Knockout.js hỗ trợ data binding 1 cách tự động giữa HTML và JavaScript nhưng chúng ta vẫn cần chỉ rõ sự phụ thuộc lẫn nhau giữa chúng. Trong khi đó, AngularJS sẽ tự động tính toán những thay đổi của những biến bị phụ thuộc mà không cần chỉ rõ mối quan hệ phụ thuộc, điều đó làm cho code của AngularJS trở nên đơn giản hơn.

Điểm độc đáo của AngularJS là nó cho phép liên kết các input/output trên HTML với controller của JavaScript 1 cách rất đơn giản bằng việc sử dụng chỉ thị ng-model và biểu thức (Expression) {{}}. Bất cứ khi nào có thay đổi, AngularJS có khả năng phát hiện những thay đổi đó và tự động cập nhật các biến hay các expression liên quan.

5 React.js

JavaScript

var MyApp = React.createClass({
  getInitialState: function(){
      return {
          firstName: this.props.firstName,
          lastName:  this.props.lastName,
      }
  },
  handleChange: function(){
      var firstName = this.refs.firstName.getDOMNode().value;
      var lastName = this.refs.lastName.getDOMNode().value;
      this.setState({
          firstName: firstName,
          lastName: lastName,
              });
  },
  render: function() {
    var fullName = this.state.firstName + this.state.lastName;
    return (
        <div>
        First name: <input ref="firstName" onChange={this.handleChange} value={this.state.firstName}/><br/>
        Last name: <input ref="lastName" onChange={this.handleChange} value={this.state.lastName}/><br/>
        Full name: {fullName}
    </div>);
  }
});

React.render(<MyApp firstName="Taro" lastName="Yamada" />, document.body);

Demo: http://jsfiddle.net/yoshiokatsuneo/k5d6jhhb/

Có thể nói React.js là 1 thư viện chuyên dụng cho data binding, nó thậm chí còn đơn giản hóa việc liên kết dữ liệu thông qua VirtualDOM.

React.js quản lí, theo dõi trạng thái của DOM và sẽ tự động phát hiện những thay đổi của DOM. Nhờ đó những người phát triển web không cần tự quản lí thay đổi của DOM 1 cách thủ công.

Nhìn đoạn code trên chúng ta có thể thấy React.js nhúng HTML vào trong JavaScript. HTML được nhúng này sẽ không trực tiếp cập nhật lên DOM thật trên HTML, thay vào đó nó được lưu trữ như các DOM ảo (VirtualDOM). Khi cần thay đổi trạng thái có thể dùng hàm setState(), khi đó React.js sẽ phát hiện những thay đổi trên VirtualDOM và cập nhật lên HTML. Vì vậy React.js có thể giúp cải thiện hiệu năng, bởi nó không thực tiếp thao tác trên DOM mà chỉ thao tác với DOM ảo và cập nhật khi có thay đổi.

6 Ractive.js

HTML

<script type="text/reactive" id="tpl">
First Name:<input type="text" value="{{firstName}}"/><br/>
Last Name:<input type="text" value="{{lastName}}"/><br/>
Full Name: {{fullName()}}<br/>
</script>
<div id='container'></div>

JavaScript

var ractive = new Ractive({
  el: 'container',
  template: '#tpl',
  data: {
    firstName: 'Taro',
    lastName: 'Yamada',
    fullName: function () {
        return this.get( 'firstName' ) + ' ' + this.get( 'lastName' );
    }
  },
});

Demo: http://jsfiddle.net/yoshiokatsuneo/d8dkppdb/

Giống như React.js, Ractive.js cũng là 1 thư viện chuyên dụng cho data binding. Tuy nhiên, nếu như React.js tập trung vào JavaScript thì Ractive.js lại tập trung vào HTML và đơn giản hóa code JavaScript. Ở HTML chúng ta sử dụng cú pháp {{}} để chỉ định việc liên kết dữ liệu đến model của JavaScript.

7 Vue.js

HTML

<div id="person">
    First Name: <input v-model="firstName"><br/>
    Last Name: <input v-model="lastName"><br/>
    Full Name: {{fullName}}<br/>
</div>

JavaScript

var demo = new Vue({
    el: '#person',
    data: {
        firstName: 'Taro',
        lastName: 'Yamada',
    },
    computed: {
        fullName: {
            get: function(){
                return this.firstName + ' ' + this.lastName;
            }
        }
    },
})

Demo: http://jsfiddle.net/yoshiokatsuneo/3gdzaw94/

Vue.js hỗ trợ 2-way data binding theo cách thức đơn giản nhất có thể. Thậm chí so với Ractive.js, HTML còn được viết 1 cách tự nhiên hơn.

Các input được gán thuộc tính v-model hay các output có chứa {{}} ở HTML được liên kết trực tiếp với với các biến và các hàm trong model của JavaScript. Sự đơn giản hóa rất hữu ích khi chúng ta muốn thiết kế HTML, prototype hay các ứng dụng nhỏ.

8 Aurelia.js

HTML

<template>
  <section>
    <form role="form">
      First Name: <input type="text" value.bind="firstName"><br/>
      Last Name: <input type="text" value.bind="lastName"><br/>
      Full name: ${fullName}
    </form>
  </section>
</template>

JavaScript

export class Welcome{
  constructor(){
    this.firstName = 'Taro';
    this.lastName = 'Yamada';
  }
  get fullName(){
    return `${this.firstName} ${this.lastName}`;
  }
}

Aurelia.js là 1 full-stack framework hỗ trợ đầy đủ các tính năng mới nhất của ECMAScript6/7.

Aurelia.js không chỉ implement các tính năng tương tự AngularJS như 2-way data binding, routing, RESTful API, DI, nó còn đơn giản và cho hiệu năng tốt hơn giống như Ractive.js/Vue.js bằng việc sử dụng những tính năng của ES6/ES7 như model hay Object.observe().

Aurelia.js là 1 trong những framework mới nhất đón đầu ES6 sẽ được giới thiệu trong năm nay. Bằng việc sử dụng polyfill, nó có thể hoạt động trên cả những trình duyệt chưa hỗ trợ ES6.

Ở đoạn code trên, HTML template được viết tuân theo chuẩn của ES6, data được nhúng vào bằng ${}value.bind. Model cũng chính là class của ES6, các thuộc tính và các hàm get/put của this được bind 2 chiều với dữ liệu trên HTML.

9 AngularJS2.0 (alpha)

HTML

First Name: <input type=text [value]="firstName" #first (keyup)="firstNameChanged($event, first)"><br/>
Last Name:  <input type=text [value]="lastName"  #last  (keyup)="lastNameChanged($event, last)"><br/>
Full Name:  {{fullName}}

JavaScript

import {Component, Template, bootstrap} from 'angular2/angular2';

// Annotation section
@Component({
  selector: 'my-app'
})
@Template({
  url: 'app.html'
})
// Component controller
class MyAppComponent {
  constructor() {
    this.firstName = 'Taro';
    this.lastName = 'Yamada';
    this.updateFullname();
  }
  changed($event, el){
    console.log("changes", this.name, el.value);
    this.name = el.value;
  }
  updateFullname(){
    this.fullName = this.firstName + " " + this.lastName;
  }
  firstNameChanged($event, first){
    this.firstName = first.value;
    this.updateFullname();
  }
  lastNameChanged($event, last){
    this.lastName = last.value;
    this.updateFullname();
  }
}
bootstrap(MyAppComponent);

AngularJS 2.0 (phiên bản alpha) là 1 framework còn đang được phát tiển, nó mới được giới thiệu bởi trang web http://angular.io tại hội thảo ng-conf về AngularJS gần đây.

AngularJS 2.0 cũng được phát triển dựa trên ES6 như Aurelia.JS. Nó sử dụng AtScript, 1 loại ngôn ngữ scripting được phát triển bởi Google nhưng kế thừa từ Typescript của Microsoft. AtScript đã thêm static type checking và annotation (cú pháp @) vào JavaScript, đồng thời nó giúp đơn giản hóa việc phát hiện lỗi và việc tích hợp vào các IDE.

Một trong những điểm thay đổi của AngularJS 2.0 là nó đã loại bỏ 2-way data binding 1 cách tự động như ở AngularJS 1.x. Chúng ta sử dụng @Component/@Template để chỉ định phần tử hay template tương ứng với từng component.

Một sự thay đổi nữa là chỉ thị ng-xxx đã bị loại bỏ, và AngularJS 2.0 đã giới thiệu những cú pháp mới như [expression] để truyền dữ liệu từ JavaScript đến HTML,(event) truyền event từ HTML đến JavaScript, #element để tham chiếu đến các phần tử. Scope cũng không còn thấy ở AngularJS 2.0 nữa, thay vào đó chúng ta có thể tham chiếu trực tiếp qua biến this.

Tổng kết

Như vậy là trong bài viết này tôi đã giới thiệu về 9 thư viện/framework JavaScript phổ biến hiện nay.

Có thể nói về các đối tượng mà các framework này nhắm tới như sau: Backbone.js dành cho những ứng dụng lớn cần customize nhiều, Ember.js/Knockout.js dành cho những ứng dụng mà hiệu năng là yêu cầu tiên quyết, AngularJS có thể dùng cho hầu hết các ứng dụng nói chung, React.js dành cho những ứng dụng muốn tập trung vào việc viết JavaScript, Ractive.js/Vue.js thì dành cho những ứng dụng tập trung vào design/HTML, Aurelia.js dành cho tương lai khi mà ES6 đã sẵn sàng, còn AngularJS 2.0 có lẽ bây giờ hợp nhất cho việc nghiên cứu về các framework.

Mỗi framework tất nhiên đều có những đặc tính riêng, nên ngay cả khi bạn đã quyết định dùng 1 framework nào đó cho project của mình thì cũng đừng ngại tìm hiểu về những framework khác. Điều đó hẳn sẽ rất hữu ích cho cách suy nghĩ và cách code của bạn.

Cảm ơn các bạn đã theo dõi bài viết.