Reactjs - Bài 17 - Tìm hiểu Jest Framework là gì

Goal

Giới thiệu Reactjs Testing Framework: Jest

Testing Framework

Phần TestUtils đã cho bạn khái niệm về các phương thức test React Component, bây giờ chúng ta sẽ thử ứng dụng Testing Framework.

Mặc dù khi tìm hiểu các bài viết về Reactjs Testing Framework, tôi có thấy nhiều ý kiến cho rằng Jest không bằng các Javascript Testing Framework khác như Mocha hay Karma, nhưng vì theo tôi đang viết theo series về Reactjs nên ở đây tôi sẽ tìm hiểu và giới thiệu Jest.

Jest

From Offical Page:

Jest provides you with multiple layers on top of Jasmine:

1.Automatically finds tests to execute in your repo
2.Automatically mocks dependencies for you when running your tests
3.Allows you to test asynchronous code synchronously
4.Runs your tests with a fake DOM implementation (via jsdom) so that your tests can run on the command line
5.Runs tests in parallel processes so that they finish sooner

Mock trong Jest

Tôi nghĩ tôi cần giải thích sơ qua Mock trong Jest.
Mock có nghĩa là làm giả, làm nhái. Để làm quen từ ngữ tôi sẽ dùng từ mock với nghĩa trên.Jest có thể mock các modules, functions, component thành một “mocked version” rồi sử dụng các Mock này mà không phải đụng đến “real version” của nó.

Mock API Reference

jest.mock(moduleName)

-> should always return a mocked version of the specified module from require() (e.g. that it should never return the real module).

jest.dontMock(moduleName)

-> should never return a mocked version of the specified module from require() (e.g. that it should always return the real module)

Getting Started:

Tôi sẽ thử test cái source code trong bài trước là Reusable Component.

Setup

Trước mắt, cần setup source code như sau:

  1. Copy toàn bộ thư mục “material-ui-component” thành thư mục “testing”.
  2. Tách source code trong file main.jsx thành các Component như member.jsx.
  3. Tạo thư mục test để chứa các file test
  4. Tạo npm test command:

    Trong thư mục “testing”:

    npm init

    để tạo file package.json (File này sẽ có vai trò chứa những config cho Jest sau này), khi init nhớ nhập ‘jest’ cho line test command: jest

  5. JSX transformer: vì source code “material-ui-component” viết theo syntax JSX nên khi test ta cũng cần chuyển đổi JSX thành JS, trong file package.json ta thêm đoạn code sau:

    "jest": {
    "scriptPreprocessor": "preprocessor.js"
    }

Và tạo file preprocessor.js

var ReactTools = require('react-tools');
module.exports = {
  process: function(src) {
    return ReactTools.transform(src, {harmony: true});
  }
};

Bạn cũng cần install react-tools.
6. Remove Mock

Trong file package.json, ta thêm như sau để chỉ định việc không mock tất cả các node module


"jest": {
"scriptPreprocessor": "preprocessor.js",
"unmockedModulePathPatterns": ["node_modules/react"]
},

Tạo Unit Test cho Component member

__test__/member-test.js

jest.dontMock('../member.jsx');

var React = require('react/addons');
var Member= require('../member.jsx');
var TestUtils = React.addons.TestUtils;

describe('Member component', function() {

  it('set name for member', function() {

    // Render a member with name is Foo
    var memberFoo = TestUtils.renderIntoDocument(
      <Member name="Foo" />
    );
    // Verify that it's name is Foo
    var memberName = TestUtils.findRenderedComponentWithType(memberFoo, Member);
    expect(memberName.getDOMNode().textContent).toEqual('Foo');

  });

});

Problem:

Với version nodejs tôi đang dùng là v0.11.14 thì khi chạy sample mẫu từ Jest thì OK, tuy nhiên khi thử test với Component member.jsx thì bị lỗi là: “Error: Worker process exited before responding! exit code: null, exit signal: SIGSEGV stderr:”. Và dường như đây là 1 known issue

https://github.com/facebook/jest/issues/243

Và tôi cũng đã thử với version mới nhất của nodejs tại thời điểm này là là v0.12.5 nhưng cũng không khá hơn.

Note: tôi đang dùng nvm để quản lý version của nodejs. Nhờ nó tôi dễ dàng upgrade cũng như downgrade version của nodejs.

Cũng theo một comment trên https://github.com/facebook/jest/issues/243 , tôi thử dùng nodejs 0.10 và thử retest thì OK:

#Chuyển sang node v0.10
nvm use 0.10

#Rebuild all dependencies
npm rebuild

#Run test
npm test

Bạn có thể thử thay đổi ‘Foo’ trong .toEqual('Foo') thành ‘Fool’ và thử chạy lại câu lệnh npm test, nó sẽ báo kết quả Fail.

Source code demo cho ví dụ trên:

https://github.com/phungnc/reactjs-tut-demo/tree/feature/jest/jest

Tôi cũng thử viết thêm một vài test cho member component vào file __test__/member-test.js:

https://github.com/phungnc/reactjs-tut-demo/blob/feature/jest_testing_more_complex/jest/member.jsx

Reactjs - Bài 16 - Testing - sử dụng TestUtils

Goal

Hôm nay tôi sẽ viết về Reactjs Addons TestUtils trong Reactjs.

TestUtils

http://facebook.github.io/react/docs/test-utils.html

TestUtils là một addon của Reactjs, cung cấp các method để test các React components.

Các mục như Simulate, renderIntoDocument đã viết khá rõ trong link trên. Ở đây tôi chỉ cố gắng demo 2 dạng testing là Assert và Component Testing.

Tôi có dùng lại Source Code bài Addon ReactLink để demo, lý do là nó đã có include sẵn React Addons :).

Bạn hãy chạy đoạn code dưới và xem kết quả nhé.

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv='Content-type' content='text/html; charset=utf-8'>
  <title>Basic React TestUtils Testing</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 src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react-with-addons.js"></script>
  <script type="text/jsx">
    var Hello = React.createClass({
      render: function() {
        return (
          <div>{this.props.name}</div>
        );
      }
    });
    var OnlyMe = React.createClass({
      render: function() {
        return (
          <div>{this.props.name}</div>
        );
      }
    });
    var Link = React.createClass({
      mixins: [React.addons.LinkedStateMixin],
      getInitialState: function() {
        return {message: 'Hello!'};
      },
      handleChange: function(newValue) {
        this.setState({message: newValue});
      },
      render: function() {
       var valueLink = {
          value: this.state.message,
          requestChange: this.handleChange
        };
        var valueLinkk =this.linkState('message');
        return (
          <div>
            <Hello name="FOO "/>
            <Hello name="BAR"/>
            <OnlyMe name="OnlyMe"/>
            <p className="title">{valueLink.value}</p>
            <span className="title">World!</span>
            <span className="sub-title">My Name is C.P</span>
            <input type="text" valueLink={valueLink} />
          </div>
        );
      }
    });

    var LinkComponent = React.render(<Link />, document.body);

    // TestUtils
    var TestUtils = React.addons.TestUtils;
    // Assert
    document.write("=== Assert Testing Type ===");
    //
    document.write("<br/>");
    document.write("<br/>");
    document.write("<b>1.isElement: check if Link is an Element</b>");
    document.write("<br/>");
    document.write(">>>Result is: ",TestUtils.isElement(<Link />));
    //
    document.write("<br/>");
    document.write("<br/>");
    document.write("<b>2.isElementOfType : check if Link is is a ReactElement whose type is of a React componentClass</b>");
    document.write("<br/>");
    document.write(">>>Result is: ",TestUtils.isElementOfType(<Link />, Link));
    //
    document.write("<br/>");
    document.write("<br/>");
    document.write("<b>3.isDOMComponent: check if Link instance is a DOM component (such as a <div> or <span>)</b>");
    document.write("<br/>");
    document.write(">>>Result is: ",TestUtils.isDOMComponent(LinkComponent));
    //
    document.write("<br/>");
    document.write("<br/>");
    document.write("<b>4.isCompositeComponent : check if Link instance is a composite component (created with React.createClass())</b>");
    document.write("<br/>");
    document.write(">>>Result is: ",TestUtils.isCompositeComponent(LinkComponent));
    //
    document.write("<br/>");
    document.write("<br/>");
    document.write("<b>5.isCompositeComponentWithType : check if Link instance is a composite component (created with React.createClass()) whose type is of a React componentClass.</b>");
    document.write("<br/>");
    document.write(">>>Result is: ",TestUtils.isCompositeComponentWithType(LinkComponent, Link));
    //
    document.write("<br/>");
    document.write("<br/>");
    document.write("=== Component Testing Type ===");
    document.write("<br/>");
    document.write("<b>1.array findAllInRenderedTree(ReactComponent tree, function test)</b>");
    document.write("<br/>");
    document.write("Traverse all components in tree and accumulate all components where test(component) is true. This is not that useful on its own, but it's used as a primitive for other test utils.");
    document.write("<br/>");
    document.write(">>> Result is: ",
      TestUtils.findAllInRenderedTree(LinkComponent, 
        function(component) { return component.tagName === "P" }
      ).map(function(component){ return component.getDOMNode().textContent })
    );
    //
    document.write("<br/>");
    document.write("<br/>");
    document.write("<b>2.array scryRenderedDOMComponentsWithClass(ReactComponent tree, string className)</b>");
    document.write("<br/>");
    document.write("Finds all instances of components in the rendered tree that are DOM components with the class name matching className");
    document.write("<br/>");
    document.write(">>> Result is: ",TestUtils.scryRenderedDOMComponentsWithClass(LinkComponent, 
      'title'
      ).map(function(component){ return component.getDOMNode().textContent }));
    //
    document.write("<br/>");
    document.write("<br/>");
    document.write("<b>3.ReactComponent findRenderedDOMComponentWithClass(ReactComponent tree, string className)</b>");
    document.write("<br/>");
    document.write("Like scryRenderedDOMComponentsWithClass() but expects there to be one result, and returns that one result, or throws exception if there is any other number of matches besides one.");
    document.write("<br/>");
    document.write(">>> Result is: ",TestUtils.findRenderedDOMComponentWithClass(LinkComponent, 
      'sub-title'
      ).getDOMNode().textContent);
    //
    document.write("<br/>");
    document.write("<br/>");
    document.write("<b>4.array scryRenderedDOMComponentsWithTag(ReactComponent tree, string tagName)</b>");
    document.write("<br/>");
    document.write("Finds all instances of components in the rendered tree that are DOM components with the tag name matching tagName");
    document.write("<br/>");
    document.write(">>> Result is: ",TestUtils.scryRenderedDOMComponentsWithTag(LinkComponent, 
      'span'
      ).map(function(component){ return component.getDOMNode().textContent }));
    //
    document.write("<br/>");
    document.write("<br/>");
    document.write("<b>5.ReactComponent findRenderedDOMComponentWithTag(ReactComponent tree, string tagName)</b>");
    document.write("<br/>");
    document.write("Like scryRenderedDOMComponentsWithTag() but expects there to be one result, and returns that one result, or throws exception if there is any other number of matches besides one.");
    document.write("<br/>");
    document.write(">>> Result is: ",TestUtils.findRenderedDOMComponentWithTag(LinkComponent, 
      'p'
      ).getDOMNode().textContent);
    //
    document.write("<br/>");
    document.write("<br/>");
    document.write("<b>6.array scryRenderedComponentsWithType(ReactComponent tree, function componentClass)</b>");
    document.write("<br/>");
    document.write("Finds all instances of components with type equal to componentClass");
    document.write("<br/>");
    document.write(">>> Result is: ",TestUtils.scryRenderedComponentsWithType(LinkComponent, 
      Hello
      ).map(function(component){ return component.getDOMNode().textContent }));
    //
    document.write("<br/>");
    document.write("<br/>");
    document.write("<b>7.ReactComponent findRenderedComponentWithType(ReactComponent tree, function componentClass)</b>");
    document.write("<br/>");
    document.write("Same as scryRenderedComponentsWithType() but expects there to be one result and returns that one result, or throws exception if there is any other number of matches besides one.");
    document.write("<br/>");
    document.write(">>> Result is: ",TestUtils.findRenderedComponentWithType(LinkComponent, 
      OnlyMe
      ).getDOMNode().textContent);
    //
  </script>
</body>
</html>

Reactjs - Bài 15 - Tái sử dụng Component Material UI trong Reactjs

Goal

Giới thiệu việc sử dụng Component có sẵn cho Reactjs.

Reusalbe Components

“When designing interfaces, break down the common design elements (buttons, form fields, layout components, etc.) into reusable components with well-defined interfaces. That way, the next time you need to build some UI, you can write much less code. This means faster development time, fewer bugs, and fewer bytes down the wire.”
– facebook –
Lược dịch:
Khi design giao diện, hãy break down những design elements như button, form fields, layout components, v.v… thành các components có thể tái sử dụng và well-defined interfaces (I/F của components). Với cách này, bạn không cần tốn nhiều thời gian cho lần tạo giao diện tiếp theo. Nghĩa là thời gian phát triển nhanh hơn, ít bug hơn, và giảm bớt wire.

Tìm React Component ở đâu?

Bạn có thể tìm kiếm các React Component ở site bên dưới này.

ReactComponent

Đây là site tập hợp các ReactComponent.
http://react-components.com/

React Rocks

http://react.rocks/

Github wiki

https://github.com/facebook/react/wiki/Complementary-Tools#ui-components

Thử sử dụng một ReactComponent

Hiện tại trên trang http://react-components.com/, tôi sẽ thử sử dụng một Component phổ biến nhất là material-ui để tạo giao diện cho demo Avatar.
Source code:
https://github.com/phungnc/reactjs-tut-demo/tree/feature/material-ui-component/material-ui-component
Trong source code mới này:
1. Vì material-ui đã có Componet Avatar, nên tôi sẽ đổi trên các Component trong demo trước từ Avatar thành Member.
2. Các file delete.jsx, toggle-star.jsx là các Icon Component tôi download từ https://github.com/callemall/material-ui/tree/master/src/svg-icons
3. Tôi vẫn dùng Browserify cho nên bạn vẫn phải dùng lệnh browserify -t reactify avatar.jsx > bundle.js để bundle up file bundle.js.
4. Khi thực hiện browserify nếu xảy ra lỗi thiếu module nào thì hãy install module đó.

Reactjs - Bài 14 - React router

Reactjs - router

[Update ngày 21/06/2016]
Nội dung bài viết bên dưới cách đây 1 năm, hiện tại react-router đã update rất nhiều, nên tôi khuyến khích các bạn hãy cập nhật nội dung về  react-router tại
https://github.com/reactjs/react-router-tutorial/tree/master/lessons

Tuy nhiên nội dung bên dưới vẫn có giá trị tham khảo, đặc biệt là khi có và không có react-router.

Goal

Giới thiệu react-router.

Giới thiệu

Nếu các bạn đã trải qua các bài viết từ bài đầu tiên đến giờ có lẽ bạn cũng đã nhận ra rằng React.js chỉ là thư viện để tạo các Component, nó không có Router.
react-router được dùng để giúp việc dẫn hướng UI đồng bộ với URL.
Để giúp các bạn hiểu rõ hơn react-router, chúng ta sẽ thử implement 2 tình huống như sau
- Without react-router
- With react-router
- Server rendering react-router
Cách này tôi dựa trên ý tưởng theo tài liệu của React Router nhưng được trình bày lại theo luồng source code demo từ những bài trước, cho nên nếu việc tham khảo trực tiếp thông qua tài liệu của React Router khiến bạn dễ hiểu hơn thì bạn nên tham khảo nó.
Ý tưởng demo như sau:
Chúng ta sẽ sử dụng source code Avatar list, và thêm trang detail cho từng Avatar. Có nghĩa là từ list Avatar, click vào link của từng Avatar thì nó chuyển sang trang Avatar detail của Avatar đó.

Without react-router

Thêm đoạn code xử lý load nội dung Avatar list hay Avatar Item theo route:
var App = React.createClass({
  render(){
    var Child;
    switch (this.props.route) {
      case 'avatars': Child = Avatars; break;
      case 'avatar': Child = Avatar; break;
      default: Child = Avatars;
    }
    return(
      <div>
        <Child />
      </div>
    )
  }
});

function render(){
  var route = window.location.hash.substr(1);
  React.render(<App route = {route}/>, document.body);
}
window.addEventListener('hashchange', render);
render();
App sẽ render các nội dung khác nhau tùy theo giá trị this.props.route. Nếu chỉ 2 trường hợp như trên thì còn khá là đơn giản nhưng trong trường hợp nhiều trang hơn, nhiều cấp cho route hơn thì vấn đề sẽ nhanh chóng trở nên phức tạp.
Source code minh họa:
https://github.com/phungnc/reactjs-tut-demo/tree/feature/route_without_react_router/route-without-react-router
Lưu ý: Vì tôi đã giới thiệu việc dùng browserify trong bài trước, nên từ bây giờ tôi sẽ dùng browserify cho việc require các module cần thiết. Cho bạn cần dùng câu lệnh sau để build file bundle.js
browserify -t reactify avatar.jsx > bundle.js

With react-router

Phần xử lý route sẽ trở thành:
var routes  = (
  <Route handler={App}>
    <DefaultRoute handler={Avatars} />
    <Route name="avatars" handler={Avatars} />
    <Route name="avatar" handler={Avatar} />
  </Route>
);

var App = React.createClass({
  render(){
    return(
      <div>
        <RouteHandler/>
      </div>
    )
  }
});
//Finally we need to listen to the url and render the application.
Router.run(routes, Router.HashLocation, function(Root) {
  React.render(<Root/>, document.body);
});
Nhớ là bạn cần ‘require’ react-router Router = require('react-router'); ở đầu file.
Source code:
https://github.com/phungnc/reactjs-tut-demo/tree/feature/route_with_react_router/route-with-react-router
Trong phần này, bạn cần đọc thêm các nội dung ở link này:
https://github.com/reactjs/react-router-tutorial/tree/master/lessons

Server-side rendering and react-router

React-router chẳng những có thể dùng ở phía client mà còn dùng được ngay cả khi render React App ở phía Server.
Nếu bạn hiểu sơ qua nguyên lý hoạt động React-router ở trên và đã đọc qua bài viết Server-side rendering thì việc modify lại code dùng React-router cho phía server sẽ không có vấn đề gì. Chỉ cần chú ý một chút là ở Sever chúng ta bundle up file browser.js
browserify -t reactify browser.js > bundle.js
Source code:
https://github.com/phungnc/reactjs-tut-demo/tree/feature/server_side_react_router/server-rendering-react-router

Reactjs - Bài 13 - server-side renderring

Goal

Trong các bài trước chúng ta chỉ sử dụng Reactjs ở phía Client, chúng ta có thể render HTML hoàn toàn ở phía Client. Trong bài này tôi sẽ giới thiệu cách ứng dụng Reactjs trong việc render HTML ở phía Server.

Client-side rendering

Trong các ví dụ trước, chúng ta render View hoàn toàn ở phía client nhờ vào method React.render(). Hãy xem hình minh họa bên dưới

Client-side-rendering

Để giúp các bạn hiểu rõ quá trình chuyển việc render từ phía client sang phía server cho nên tôi sẽ liệt kê các bộ phận cũng như từng bước của việc render ở phía Client như sau:

  1. File index.html: layout của view, chứa DOM để gắn ReactElement vào (trong các bài trước, tôi gắn React Element vào thẻ ).
  2. Include các file react.js, JSXTransformer.js,(ngoài ra còn có các file khác như superagent.js hay React-addon…)
  3. React.createClass() định nghĩa Virtual DOM
  4. React.createElement() tạo Virtual DOM
  5. React.render() render Virtual DOM vào thẻ
  6. Có một được một trang index.html hoàn chỉnh.

Source code mẫu cho Client-side rendering:

<!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 src="https://cdnjs.cloudflare.com/ajax/libs/superagent/0.15.7/superagent.min.js"></script>
  <script type="text/jsx">

    var Avatar = React.createClass({
      propTypes: {
        id: React.PropTypes.string.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 request = window.superagent;
    var Avatars = React.createClass({

      getInitialState() {
        return {
          avatars: [
            {id: 1, name: "Avatar 1", height: 100, width: 100, like: false, src: "http://canime.files.wordpress.com/2010/05/mask-dtb.jpg"},
            {id: 2, name: "Avatar 2", height: 100, width: 100, like: true, src: "http://z4.ifrm.com/30544/116/0/a3359905/avatar-3359905.jpg"},
            {id: 3, name: "Avatar 3", height: 100, width: 100, like: 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;
          })
        });
      },
      componentDidMount: function() {
        var self = this;
        request.get('http://localhost:3000/api/employees', function(res) {
        console.log(res);
          self.setState({avatars: res.body});
        });
      },

      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.like} />;
        }, this);
        return (
          <ul>
            {avatars}
          </ul>
        );
      }
    });

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

Server-side rendering

Server-side rendering thực chất là việc tạo ra và trả về browser 1 trang index.html hoàn chỉnh.

Server-side-rendering

Để thực hiện điều đó chúng ta cũng cần các bước trên, nhưng sẽ có một số biến đổi tương đượng như sau:

  1. File index.html
    Chúng ta sẽ dùng ngôn ngữ template để gắn ReactElement vào vị trí DOM cần thiết (vì ở server thì chưa có Real DOM để chúng ta get và gắn vào). Ở đây tôi dùng ngôn ngữ template là jade, cho nên chúng ta cũng sẽ có file index.jade. File này cũng chứa layout của View

  2. Include các file react.js, JSXTransformer.js
    Tôi dùng nodejs, nên tôi sẽ dùng method require() để include các package tương ứng.

  3. React.createClass() không thay đổi

  4. React.createElement() không thay đổi
  5. React.render() ở phía client có 2 ý nghĩa là render Virtual DOM thành HTML (hay real DOM) và gắn vào thẻ . Điều đó, phía server sẽ thực hiện một cách tương tự như sau:

    • Dùng React.renderToString. (Vui lòng tham khảo method này để hiểu thêm) để trả về một HTML string (chứa nội dung HTML mà ta muốn render).
    • Truyền HTML string đó vào thông qua biến !{markup}. Jade engine sẽ render ra index.html hoàn chỉnh
  6. Trả về cho Browser trang index.html vừa tạo ra ở trên.

Server-side-rendering-static

Source code:

https://github.com/phungnc/reactjs-tut-demo/tree/feature/server_side_rendering_static/server-rendering-static

Lưu ý: bạn hãy tự cài những module còn thiếu nhé, vì có những module tôi dùng như là global module nên khi viết source này tôi không có cài lại.

Sau khi bạn clone về, bạn start cái server bằng lệnh:

node server

và vào link:

http://localhost:5000

Boom! bạn đã có được một View giống như khi render ở phía client.
Tuy nhiên khi bạn thử click và button như like hay là delete thì chẳng có điều gì xảy ra. Tại sao vậy?

Tại vì thực ra server chỉ trả về cho Browser 1 file HTML tĩnh có “gắn” phần HTML vừa được React tạo ra vào thẻ <body> thôi chứ không có phần source code JS đi kèm theo. Điều này khác với việc render ở Client là bản thân souce code JS đã include sẵn. Cho nên bây giờ ta tiếp tục “trả” về cho client source code JS đó.

Để thực hiện điều này, tôi dùng browserify để “write code that uses require in the same way that you would use it in Node”. Cho nên chúng ta sẽ tận dụng cách viết ở phía Node Server cho phía Client. Browserify sẽ bundle up (tập hợp) tất cả các module cần thiết định nghĩa trong file mà chúng ta tạm gọi là browser.js thành file bundle.js và gửi cho Client.

Hình mình họa:

server-side rendering dynamic

Bây giờ, chúng ta sẽ tạo một file là browser.js để thông qua nó Browserify tạo file bundle.js:

'use strict';
var React   = require('react'),
    Avatar = require('./avatar');
React.render(<Avatar />, document.body);

Vì ở đây chúng ta dùng JSX syntax cho nên chúng ta cũng cần reactify để giúp Browserify transform JSX.

Bạn có thể thử command như bên dưới để thử bundle up file bundle.js từ file browser.js ở trên:

browserify -t reactify browser.js > bundle.js

Khi bạn mở file bundle.js, bạn sẽ thấy file bundle.js này đã gộp Reactjs và file avatar.js lại thành một.

OK, chúng ta đã có bundle.js giờ chỉ cần gửi file này về cho browser bằng cách thêm đoạn code sau vào bên dưới body trong file index.jade

    |     
    script(src='./bundle.js')

Và thêm dòng code sau app.use(Express.static(__dirname + '/')); vào bên dưới dòng app.set('view engine', 'Jade'); ở file server.js để báo cho express.static biết là nơi chứa các file static.

Bây giờ bạn reload lại page, thử click các button thử xem, It’s work!

Source code:

https://github.com/phungnc/reactjs-tut-demo/tree/feature/server_side_rendering_dynamic/server-rendering

Lưu ý: bạn hãy tự cài những module còn thiếu nhé, vì có những module tôi dùng như là global module nên khi viết source này tôi không có install vào.

Việc sử dụng Javascript ở cả server và client người ta gọi là Isomorphic JavaScript (Javascript đồng hình)

!= Nodejs

  1. react-php-v8js
    https://github.com/reactjs/react-php-v8js

  2. react-rails
    https://github.com/reactjs/react-rails

  3. React.NET
    https://github.com/reactjs/React.NET

  4. react-python
    https://github.com/reactjs/react-python