Reactjs - Bài 12 - Addon

Goal

Giới thiệu addon trong Reactjs

1. Addon

React.addons là nơi ta đóng gói những utils dành cho việc xây dựng React apps.

Cách sử dụng:

Thêm

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react-with-addons.js"></script>

vào phần head của page.

Animation Addon.

React cung cấp addon component ReactTransitionGroup như là low-level API for animation, và một ReactCSSTransitionGroup để dễ dành implement những CSS animation và transition đơn giản.

Ví dụ

Lấy ví dụ trong bài ‘props and state’, ở ví dụ đó, ta có 1 list các Avatar. Khi click vào nút delete nào thì Avatar đó sẽ bị remove.

Bây giờ ta thử dùng Reactjs Animation Addon để tạo hiệu ứng khi ta remove một Avatar.

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv='Content-type' content='text/html; charset=utf-8'>
  <title>Basic Example Props</title>
  <style>
    .example-leave {
      opacity: 1;
      transition: opacity .5s ease-in;
    }

    .example-leave.example-leave-active {
      opacity: 0.01;
    }
  </style>
</head>
<body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.js"></script>  
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/JSXTransformer.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react-with-addons.js"></script>
  <script type="text/jsx">

    var Avatar = React.createClass({
      propTypes: {
        id: React.PropTypes.number.isRequired,
        name: React.PropTypes.string.isRequired,
        width: React.PropTypes.number.isRequired,
        height: React.PropTypes.number.isRequired,
        initialLike: React.PropTypes.bool.isRequired,
        // Thêm Interface onDelete()
        onDelete: React.PropTypes.func.isRequired,
      },
      getInitialState() {
        return {
          liked: this.props.initialLike
        };
      },
      onClick() {
        this.setState({liked: !this.state.liked});
      },
      // Ủy quyền cho Component cha xử lý.
      _onDelete() {
        this.props.onDelete(this.props.id);
      },
      render() {
        var textLike = this.state.liked ? 'like' : 'haven\'t liked';
        return (
          <li key={this.props.id}>
            <span>{this.props.id}</span>
            <img  src={this.props.src} width={this.props.width} height={this.props.height} alt="alt" />
            <span>{this.props.name}</span>
            <button onClick={this.onClick}>{textLike}</button>
            <button onClick={this._onDelete}>Delete</button>
          </li>
        );
    }
    });
    var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
    var Avatars = React.createClass({

      getInitialState() {
        return {
          avatars: [
            {id: 1, name: "Avatar 1", height: 100, width: 100, initialLike: false, src: "http://canime.files.wordpress.com/2010/05/mask-dtb.jpg"},
            {id: 2, name: "Avatar 2", height: 100, width: 100, initialLike: true, src: "http://z4.ifrm.com/30544/116/0/a3359905/avatar-3359905.jpg"},
            {id: 3, name: "Avatar 3", height: 100, width: 100, initialLike: false, src: "http://www.dodaj.rs/f/O/IM/OxPONIh/134.jpg"}
          ]
        }
      },
      // Thêm method deleteItem() set lại State (chứa các Component con) cho Component cha này
      deleteItem(id) {
        this.setState({
          avatars: this.state.avatars.filter(function(avatar){
            return avatar.id !== id;
          })
        });
      },

      render() {  
        var avatars = this.state.avatars.map(function(avatar){
        // use below solution
        // map(function(){},this) 
        // or map(function(){}.bind(this)) 
        // or var that = this; onDelete = {that.deleteUser}
        // to pass this value to map function.
        // bind onDelete (event) to deleteUser.
          return <Avatar onDelete={this.deleteItem} id={avatar.id} name={avatar.name} width={avatar.width} height={avatar.height} src={avatar.src} initialLike={avatar.initialLike} />;
        }, this);
        return (
          <ul>
            <ReactCSSTransitionGroup transitionName="example">
              {avatars}
            </ReactCSSTransitionGroup>
          </ul>
        );
      }
    });

    var AvatarsComponent = React.render(<Avatars />, document.body);
  </script>
</body>
</html>

Các điểm updated:
1. Thêm react-with-addons.js
2. Thêm <style></style>
3. Wrap {avatars} bởi ReactCSSTransitionGroup

Trong component này, khi remove 1 item ra khỏi ReactCSSTransitionGroup nó sẽ lấy example-leave CSS class và example-leave-active CSS class được add vào trong ‘tick’ tiếp theo. Cái convention này dựa vào transitionName prop (Trong ví dụ trên ta có chỉ định transitionName=”example”).

2. Two-way binding

Phần này chủ yếu là tôi lược dịch lại từ trang gốc của Reactjs. Do đó nếu bạn cảm thấy nội dung trên trang gốc dễ hiểu hơn thì bạn nên bỏ qua bài này.

Chú ý: nếu bạn là Reactjs newbie thì bạn cũng không cần phải tìm hiểu về ReactLinkReactLink chưa cần thiết cho hầu hết các ứng dụng.

Trong Reactjs, data đi theo một luồng, theo mô hình input - output, ta gọi là one way binding.

Tuy nhiên, nhiều ứng dụng thường yêu cầu khi có lấy dữ liệu từ output và truyền ngược vào trong chương trình. Ví dụ như khi bạn muốn update React state từ input của user.

Trong React, để làm điều này, trước hết bạn phải listen sự kiện “change” ( onChange() ), rồi đọc giá trị của DOM và gọi setState() để thay đổi state của component. khi state của component thay đổi thì nó sẽ re-render lại UI của component.

Reactjs mang đến cho bạn addon ReactLink, sẽ luôn ràng buộc giữa giá trị của DOM và state của component. Hãy tưởng tượng, với ReactLink bạn đã có một sợi dây “kết nối” giữa data trên DOM và React state.

Chú ý: ReactLink thực chất cũng chỉ là một wrapper gộp onChange/setState(). Do đó về nguyên tắc nó không có thay đổi bản chất data flows trong React Application.

Phần ví dụ ReactLink: Before and After minh họa khá rõ về điều này, cho có lẽ tôi không cần trình bày thêm, chỉ lưu ý là ReactLink dùng mixins để thêm method linkState() từ LinkedStateMixin vào React component.

Trong bài trên, phần Under the Hood chủ yếu giải thích sâu hơn về LinkedStateMixin, nhưng tôi nghĩ trong giới hạn một newbie thì ta chỉ cần biết là React Addon có cung cấp LinkedStateMixin dành cho việt two-way binding. Còn nếu bạn muốn hiểu sâu hơn thì tôi nghĩ đọc source code react-with-addons.js phần LinkedStateMixin sẽ dễ hiểu hơn nhiều.

Ngoài 2 addon giới thiệu ở trên, Reactjs còn cung cấp các addon khác, bạn có thể tham khảo tại link bên dưới:

https://facebook.github.io/react/docs/addons.html

Reactjs - Bài 11 - spread attribute

Goal

Giới thiệu spread attribute trong JSX syntax component.

Ví dụ dẫn nhập

Hãy trở lại ví dụ trong bài giới thiệu về props, nhưng hãy để property src đóng vai trò là một I/F của component và bỏ attribute alt đi.

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv='Content-type' content='text/html; charset=utf-8'>
  <title>Basic Example Props</title>
</head>
<body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.js"></script>  
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/JSXTransformer.js"></script>
  <script type="text/jsx">
    var Avatar = React.createClass({
      propTypes: {
        name: React.PropTypes.string.isRequired,
        width: React.PropTypes.number.isRequired,
        height: React.PropTypes.number.isRequired
      },
      render() {
        return (
          <div>
            <img src={this.props.src} width={this.props.width} height={this.props.height}/>
            <span>{this.props.name}</span>
          </div>
        );
      }
    });
    var AvatarEl = <Avatar name="Foo" width={100} height={100} src="http://canime.files.wordpress.com/2010/05/mask-dtb.jpg" />;
    var AvatarComponent = React.render(AvatarEl, document.body);
  </script>
</body>
</html> 

JSX có cung cấp cho ta một syntax gọi là spread operator giúp chúng ta viết ngắn gọn phần return img như sau:

<img src={this.props.src} width={this.props.width} height={this.props.height}/>

thành:

<img {...this.props}/>

... được gọi là spread operator.

Bây giờ, hãy mở JSX Compiler:

https://facebook.github.io/react/jsx-compiler.html

và nhập lần lượt

Cách 1:

<img src={this.props.src} width={this.props.width} height={this.props.height} />

Cách 2:

<img {...this.props}/>

vào phần bên trái, nó sẽ Output ra Javascript lần lượt như sau:

Đối với Cách 1, nó sẽ Output:

React.createElement("img", {src: this.props.src, width: this.props.width, height: this.props.height})

Đối với Cách 2, nó sẽ Output

React.createElement("img", React.__spread({},  this.props))

Bạn có thể thấy là cả 2 cách viết thì nó đều truyền props object vào method React.createElement, điểm khác nhau ở đây là với Cách 2 nếu bạn muốn truyền vào thêm thuộc tính alt cho thì bạn cũng không cần phải add thêm alt={this.props.alt} như trong cách 1. Is it cool?. Bây giờ bạn hãy thực sự dùng cách 2, và bạn có thể thêm alt=”it is cool!” vào đoạn code sau và reload lại page, check view, ¯_(ツ)_/¯ , trong đã có thêm thuộc tính alt

var AvatarEl = <Avatar name="Foo" width={100} height={100} src={"http://canime.files.wordpress.com/2010/05/mask-dtb.jpg"} alt="it is cool!"/>;

spread operator and ES6

spread operator là syntax mới của Javascript trong bộ chuẩn ES6. Nếu bạn muốn tìm hiểu thêm thì link bên dưới sẽ giúp cho bạn:

Bài viết:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

https://babeljs.io/docs/learn-es2015/#default-rest-spread

https://leanpub.com/exploring-es6/read

Tool compile từ ES6 sang Javascript version hiện tại

https://babeljs.io/repl/

Hãy sử dung tool trên để nhập cú pháp có chứa spread operator và xem nó output ra cú pháp js như thế nào.

Reactjs - Bài 10 - vấn đề về performance

Goal

Giới thiệu cấu trúc quan hệ Component và việc re-render lại cấu trúc đó khi có thay đổi.

Stateful Component vs Stateless Component

  • Stateful Component: Component có chứa state (need a memory)
  • Stateless Component: Component không chứa state (no need memory)

Stateful và Stateless Component trong Reactjs

Đặc trưng mô hình trong Reactjs là theo hướng Stateless Component.

Các Component sẽ nhận data từ Component cha, từ đó tạo nên cấu trúc View. Điểm quan trọng ở đây là tự thân Component sẽ không chứa state. Nhờ vào việc output View theo input từ bên ngoài mà việc quản lý, tái sử dụng và testable cho Componennt có thể thực hiện được.

Tất nhiên, nếu tất cả các Component không chứa state thì nó cũng giống như một trang HTML tĩnh nên việc chứa state trong Component là cần thiết nhưng mà nên giảm thiểu những Component như vậy mà cần kết hợp các Stateless Component lại với nhau.

Thông thường thì chỉ có Root Component trong cấu trúc dạng tree mới chứa state và truyền state đó xuống các component con và tạo nên cấu trúc tree.

Khi mà state của Component có sự thay đổi thì tree sẽ tự động tái cấu trúc cho nên không cần phải update lại view một cách thủ công.

Về cơ bản thì ngoài Root Component ra thì không cần phải chứa state cũng OK nhưng mà có những trường hợp cũng phải chứa trạng thái thay đổi do User action

Virtual DOM

Việc chỉ dùng Root Component chứa state và nếu state đó thay đổi thì tái cấu trúc lại toàn bộ View thì đối việc thiết kế rõ ràng sẽ đơn giản nhưng mà chỉ một phần có thay mà toàn bộ DOM tree phải re-render lại thì về mặt performance sẽ rất tệ. Cho nên Reactjs đã đưa Virtual DOM vào trong cấu trúc của mình.

Nói một cách đơn giản thì Virtual DOM là một Javascript Object chứa DOM tree. Trong trường hợp data có thay đổi thì Reactjs sẽ tính diff của Object đó rồi chỉ re-render những Real DOM ở mức tối thiểu nhất.

Trong Reactjs, React.createClass sẽ tạo Component, render() method của Component sẽ return “định nghĩa” Virtual DOM. Virtual DOM được tạo từ React.createElement or bằng syntax JSX

Reactjs Performance

Tham khảo Benchmark dưới đây về kết quả so sánh:

  1. Backbone.js: chỉ render DOM nào có thay đổi
  2. Backbone.js: render toàn bộ DOM nếu có bất cứ thay đổi nào.
  3. React.js: render phần DOM có sự thay đổi thông qua Virtual DOM khi có thay đổi gì.

TodoMVC Benchmark

Theo benchmark thì tốc độ Reactjs sẽ theo thứ tự như sau:

2 (chậm nhất) < 3 < 1 (nhanh nhất) .

Reactjs - Bài 9 - refs và cách tương tác với Browser

Goal

Tìm hiểu về refs trong reactjs và cách tương tác với browser.

Refs and findDOMNode()

Khi chúng ta tạo các component, ‘UI’ trong reactjs, thực ra chúng ta chỉ mới làm việc với faked browser, chứ không phải browser thực sự.

Để tương tác với browser, bạn sẽ cần tham chiếu đến một DOM node. React có method React.findDOMNode(component) mà bạn có thể gọi để có được một sự tham chiếu đến DOM node của component đó.

method findDOMNode() chỉ làm việc trên những component đã được mounted. Nếu bạn dùng nó khi component chưa được mounted, chẳng hạn trong phương thức render() thì sẽ xảy ra lỗi exception.

Chúng ta thử xem ví dụ mà bạn đã làm quen trong bài trước:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv='Content-type' content='text/html; charset=utf-8'>
  <title>Basic Example Props</title>
</head>
<body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.js"></script>  
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/JSXTransformer.js"></script>
  <script type="text/jsx">

    var Avatar = React.createClass({
      propTypes: {
        name: React.PropTypes.string.isRequired,
        width: React.PropTypes.number.isRequired,
        height: React.PropTypes.number.isRequired
      },

      getInitialState() {
        return {
          src: 'http://canime.files.wordpress.com/2010/05/mask-dtb.jpg'
        };
      },

      onClick() {
        this.setState({src: 'http://38.media.tumblr.com/9f96b52d8fda03c77d0b620d4f12a128/tumblr_n0lvu7fSqE1sont7fo1_500.gif'});
      },

      render() {
        //var src='http://canime.files.wordpress.com/2010/05/mask-dtb.jpg';
        return (
          <div>
            <img src={this.state.src} width={this.props.width} height={this.props.height} alt="alt" />
            <span>{this.props.name}</span>
            <button onClick={this.onClick}>So HOT!!!!</button>
          </div>
        );
      }

    });
    var AvatarEl = <Avatar name="Foo" width={100} height={100}/>;
    var AvatarComponent = React.render(AvatarEl, document.body);
  </script>
</body>
</html>

Bạn hãy mở Developer Tool, và thử in ra AvatarComponent.refs chúng ta sẽ có Object {}, một object rỗng

Bây giờ ta thử thêm ref="avatar" vào trong thẻ như sau:

<img ref="avatar" src={this.state.src} width={this.props.width} height={this.props.height} alt="alt" />

Và bạn lại thử in AvatarComponent.refs, kết quả sẽ là Object {avatar: R…s.c…s.Constructor},

Cách tạo và dùng refs

  1. Thêm attribute ref="myRef" vào thẻ trong thẻ html được trả về từ hàm render.
  2. Bạn có thể access DOM của component trực tiếp bằng cách gọi React.findDOMNode(this.refs.myRef)

More About Refs

Về phần ‘More about refs’, trang chính của Reactjs mô tả khá rõ, phần bên dưới chỉ là phần lược dịch lại theo cách mà tôi đang hiểu.

Hãy tham khảo link More About Refs

Như những bài trước đã giới thiệu, chúng ta config và quản lý state (trạng thái) Component thông qua stateprops. Mỗi khi có sự thay đổi của stateprops thì chúng ta Component đó đều render lại. Việc data thay đổi thông qua stateprops được gọi là React data flow. React data flow luôn đảm bảo hầu hết props mà được truyền cho children đều được output từ method render(). Cho nên việc tương tác UI phần lớn là thông qua props hay state, tuy nhiên có một vài trường hợp mà bạn cần tương tác với UI sau khi render. Ví dụ như khi bạn muốn dùng jQuery plugin trong ứng dụng của mình. Hay như bạn muốn element được focus sau khi bạn update giá trị của nó thành empty string ”.

Tham khảo Completing the Example

  • Một lưu ý nhỏ cho Reactjs newbie: Cố gắng nắm vững và thực hành nhiều với stateprops trước khi dùng refs, thường thì React data flow sẽ giải quyết được hết mục đích của bạn.

Reactjs - Bài 8 - Event

Goal

Giới thiệu event trong Reactjs

SyntheticEvent (Event hợp nhất)

Giống như việc Reactjs wrap DOM thành các VirtualDOM thì Reactjs cũng wrap DOM event thành các SyntheticEvent dùng chung cho các Browsers.
I/F
boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
void stopPropagation()
DOMEventTarget target
number timeStamp
string type
Theo đó, chúng ta có thể sử dụng các event preventDefault, stopPropagation hay target như hồi giờ.
Từ Reactjs v0.12 thì không dùng cách return false ở event handler để stop event propagation. Thay vào đó chúng ta dùng e.stopPropagation() or e.preventDefault() để thực hiện

Event handler

Trong bài trước, chúng ta đã có đụng đến event của Reactjs, ta thử nêu lại ở đây:
    var Avatar = React.createClass({
      propTypes: {
        name: React.PropTypes.string.isRequired,
        width: React.PropTypes.number.isRequired,
        height: React.PropTypes.number.isRequired
      },

      getInitialState() {
        return {
          //src: 'http://canime.files.wordpress.com/2010/05/mask-dtb.jpg'
          src: this.props.src
        };
      },
      // event handler
      onClick() {
        this.setState({src: 'http://38.media.tumblr.com/9f96b52d8fda03c77d0b620d4f12a128/tumblr_n0lvu7fSqE1sont7fo1_500.gif'});
      },

      render() {
        //var src='http://canime.files.wordpress.com/2010/05/mask-dtb.jpg';
        return (
          <div>
            <img  src={this.state.src} width={this.props.width} height={this.props.height} alt="alt" />
            <span>{this.props.name}</span>
            <button onClick={this.onClick}>So HOT!!!!</button>
          </div>
        );
      }

    });
onClick={this.onClick} : bind event click với event handler là onClick. Bên trong onClick, Reactjs bind this với Component instance cho nên chúng ta không cần phải ‘bind’ this vào event handler như kiểu: onClick(e) {...}.bind(this).

Not provided event

Đối với các event mà Reactjs support thì cứ sử dụng bình thường nhưng mà đối với event resize của window hay như các event riêng của jQuery plugin thì chúng ta:
  1. Sử dụng addEventListener trong method componentDidMount() để đăng ký event.
  2. Và dùng removeEventListener trong method componentWillUnmount để remove event.
    Tham khảo:
    http://facebook.github.io/react/tips/dom-event-listeners.html