11/08/2018, 20:01

React dành cho AngularJS developer (Phần 2)

Tiếp nối bài viết trước, hôm nay chúng ta cùng tiếp tục xem xét cách giải quyết vấn đề về Component, Data binding,… trong React và Angular. Component React component thì có dạng giống như Angular directive, chúng được sử dụng để trừu tượng các cấu trúc DOM phức tạp, và chia nhỏ theo ...

Tiếp nối bài viết trước, hôm nay chúng ta cùng tiếp tục xem xét cách giải quyết vấn đề về Component, Data binding,… trong React và Angular.

Component

React component thì có dạng giống như Angular directive, chúng được sử dụng để trừu tượng các cấu trúc DOM phức tạp, và chia nhỏ theo hành vi để có thể tái sử dụng. Ví dụ, để làm một slide show với Angular.

// index.html
<div ng-controller="SlideShowController">
  <slide-show slides="slides"></slide-show>
</div>

// slide-show.html
<div class="slideshow">
  <ul class="slideshow-slides">
  <li ng-repeat="slide in slides" ng-class="{ active: $index == activeIndex }">
    <figure>
      <img ng-src="{{ slide.imageUrl }}" />
      <figcaption ng-show="slide.caption">{{ slide.caption }}</figcaption>
    </figure>
  </li>
  </ul>
  <ul class="slideshow-dots">
    <li ng-repeat="slide in slides" ng-class="{ active: $index == activeIndex }">
      <a ng-click="jumpToSlide($index)">{{ $index + 1 }}</a>
    </li>
  </ul>
</div>

// js
app.controller("SlideShowController", function($scope) {
  $scope.slides = [{
    imageUrl: "image.jpg",
    caption: "This is image"
  }, {
    imageUrl: "image.jpg",
    caption: "This is image"
  }];
});

app.directive("slideShow", function() {
  return {
    restrict: 'E',
    scope: {
      slides: '='
    },
    template: 'slide-show.html',
    link: function($scope, element, attrs) {
      $scope.activeIndex = 0;
      $scope.jumpToSlide = function(index) {
        $scope.activeIndex = index;
      };
    }
  };
});

Trong khi có với React, component sẽ được render bên trong component khác, trao đổi dữ liệu với nhau thông qua props.

let _slides = [{
  imageUrl: "image.jpg",
  caption: "This is image"
}, {
  imageUrl: "image.jpg",
  caption: "This is image"
}];

class App extends React.Component {
  render() {
    return <SlideShow slides={ _slides } />
  }
}

class SlideShow extends React.Component {
  constructor() {
    super()
    this.state = { activeIndex: 0 };
  }
  jumpToSlide(index) {
    this.setState({ activeIndex: index });
  }
  render() {
    return (
      <div className="slideshow">
        <ul className="slideshow-slides">
          {
            this.props.slides.map((slide, index) => (
              <li className={ classNames({ active: index == this.state.activeIndex }) }>
                <figure>
                  <img src={ slide.imageUrl } />
                  { slide.caption ? <figcaption>{ slide.caption }</figcaption> : null }
                </figure>
              </li>
            ))
          }
        </ul>
        <ul className="slideshow-dots">
          {
            this.props.slides.map((slide, index) => (
              <li className={ (index == this.state.activeIndex) ? 'active': ' }>
                <a onClick={ (event)=> this.jumpToSlide(index) }>{ index + 1 }</a>
              </li>
            ))
          }
        </ul>
      </div>
    );
  }
}

React component chứa state bên trong nó (this.state), bạn có thể modify bằng cách gọi this.setState({ key: value}). Bất cứ sự thay đổi nào liên quan tới state của component đều khiến nó tự render lại.

Event trong React nhìn thì có vẻ như old-school style, dạng như inline event handler như kiểu onClick hay tương tự. Nhưng đừng vì thế mà cảm thấy nó chuối, bởi vì thực ra React đang cố gắng gain performance từ những điểm này, việc này tạo ra high performance delegated event listeners.

Two-Way Binding

Angular sử dụng ng-model và $scope để tạo ra liên kết giữa các luồng dữ liệu được trao đổi qua lại giữa form element và các property của Javascript object bên trong controller.

// Javascript
app.controller("TwoWayController", function($scope) {
  $scope.person = {
    name: 'Hoang'
  };
});

// HTML
<div ng-controller="TwoWayController">
  <input ng-model="person.name" />
  <p>Hello {{ person.name }}!</p>
</div>

React không sử dụng mô hình này, thay vào đó sử dụng one-way data flow.

class OneWayComponent extends React.Component {
  constructor() {
    super()
    this.state = { name: 'Hoang' }
  }
  change(event) {
    this.setState({ name: event.target.value });
  }
  render() {
    return (
      <div>
        <input value={ this.state.name } onChange={ (event)=> this.change(event) } />
        <p>Hello { this.state.name }!</p>
      </div>
    );
  }
}

Tag trong đoạn code trên được gọi là controlled input, có nghĩa là bất cứ khi nào giá trị của controlled input bị thay đổi thì hàm render sẽ được gọi. Bản thân component được gọi là stateful bởi vì nó tự manage data của chính nó. Điều này thì không được recommend cho phần lớn các component, vì thế ý tưởng là đễ giữ cho component , ta sẽ trap đổi data thông qua props.

Thông thường, một stateful container component hay là controller view thường nằm trên top của cây DOM với hàng loạt các statless component con nằm bên dưới, để hiểu rõ hơn các bạn có thể tìm hiểu tại sao component nên có state ở đây https://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html#what-components-should-have-state.

Sự linh hoạt

Với one-way down data flow, dữ liệu chỉ có một chiều duy nhất vì thế dễ dàng hơn cho chúng ta gọi các phương thức của component cha thông qua callback. Sự linh hoạt này khiến cho developer có khả năng control được rất nhiều thứ khi thực hiện refactor component để tạo cho các thành phần hiển thị trở nên đơn giản nhất. Nếu component đã được refactor không chứa bất cứ state nào, thì ta có thể chuyển thành pure function. Nghe thì có vẻ khó hiểu, nhưng các bạn có thể hình tượng qua đoạn code sau.

const OneWayComponent = (props)=> (
  <div>
    <input value={ props.name } onChange={ (event)=> props.onChange(event.target.value) } />
    <p>Hello { props.name }!</p>
  </div>
);

class ParentComponent extends React.Component {
  constructor() {
    super()
    this.state = { name: 'Hoang' };
  }
  change(value) {
    this.setState({name: value});
  }
  render() {
    return (
      <div>
        <OneWayComponent name={ this.state.name } onChange={ this.change.bind(this) } />
        <p>Hello { this.state.name }!</p>
      </div>
    )
  }
}

Điều này có vẻ là khá khó chịu đối với những người đã quen với việc sử dụng two-way data binding. Lợi ích của việc có nhiều thành phần nhỏ trong tầng hiển thị, mà các thành phần đó chỉ chấp nhận data đưa vào là props và render chúng như một hành động mặc định, và từ đó vì các component là vô cùng đơn giản nên có thể giảm thiểu được số lượng bug, càng ít code, càng ít bug mà hahaha. Điều này cũng ngăn cản việc UI bị bất ổn định, điều mà thường xảy ra khi dữ liệu được đặt và maintain ở nhiều nơi khác nhau.

Dependency Injection, Services, Filters

Ngày nay, việc quản lý dependency trong Javascript tiện lợi hơn bao giờ hết nhờ có Webpack hay Browserify. Dưới đây là so sánh việc sử dụng dependency trong Angular và React, với ES6 việc các câu lệnh trở nên gần gũi với các ngôn ngữ class based hơn nhiều.

// An Angular directive with dependencies
app.directive('myComponent', ['Notifier', '$filter', function(Notifier, $filter) {
  const formatName = $filter('formatName');
  // use Notifier / formatName
}]

// ES6 Modules used by a React component
import Notifier from "services/notifier";
import { formatName } from "filters";

class MyComponent extends React.Component {

  // use Notifier / formatName

}

Nói tóm lại

Tóm lại, nếu bạn đang gặp vấn đề với rendering performance, một vấn đề thường gặp khi sử dụng Angular trong application, bạn có thể boost performance bằng cách chuyển việc rendering sang sử dụng React. Cũng không quá phức tạp khi sử dụng nhiều library để giải quyết cùng một vấn đề trong một application.

Trong khi React và Angular giải quyết một số vấn đề giống nhau, nhưng cách giải quyết lại hoàn toàn khác nhau. React ủng hộ phương thức khai báo function khi mà các component là các pure function giúp hạn chế lỗi.

Qua bài viết này, hy vọng các bạn đã có những cái nhìn cơ bản về cách giải quyết vấn đề của React và Angular, cũng như có thể giúp các bạn switch context dễ dàng hơn.

Tạm biệt và hẹn gặp lại trong các bài viết tiếp theo.

Bài gốc: https://codeaholicguy.wordpress.com/2016/01/21/react-danh-cho-angularjs-developer-phan-2/

0