Search code examples
node.jssocket.ioreactjsfluximmutable.js

How to render the React component with dynamic data realtime from socket.io high efficiency


My front-end page is made by React + Flux, which sends the script data to back-end nodejs server.

The script data is an Array which contains the linux shell arguments (more than 100000). When to back-end received, it will execute the linux shell command.

Just an example:

cat ~/testfile1
cat ~/testfile2
          .
          .
          .
(100000 times ...etc)

When the backend finished one of the linux shell commands, I can save the readed content to result data. Therefore, socket.io will emit the result data to the front-end.

I want to get the result data from my webpage in real time, so I have done some stuff in my project below.

My React component code:

import React from 'react';

import AppActions from '../../../actions/app-actions';
import SocketStore from '../../../stores/socket-store';
import ResultStore from '../../../stores/result-store';

function getSocket () {
    return SocketStore.getSocket();
}

function getResult () {
    return ResultStore.getResultItem();
}

class ListResultItem extends React.Component {
    constructor () {
        super();
    }
    render () {
        return <li>
                {this.props.result.get('name')} {this.props.result.get('txt')}
            </li>;
    }
}

class ShowResult extends React.Component {
    constructor () {
        super();
        this.state = {
            socket: getSocket(),
            result: getResult()
        };
    }
    componentWillMount () {
        ResultStore.addChangeListener(this._onChange.bind(this));
    }
    _onChange () {
        this.setState({
            result: getResult()
        });
    }
    render () {
        return <div>
                <ol>
                    {this.state.result.map(function(item, index) {
                        return <ListResultItem key={index} result={item} />;
                    })}
                </ol>
            </div>;
    }
    componentDidMount () {
        this.state.socket.on('result', function (data) {
            AppActions.addResult(data);
        });
    }
}

My Flux store (ResultStore) code:

import AppConstants from '../constants/app-constants.js';
import { dispatch, register } from '../dispatchers/app-dispatcher.js';
import { EventEmitter } from 'events';
import Immutable from 'immutable';

const CHANGE_EVENT = 'changeResult';

let _resultItem = Immutable.List();

const _addResult = (result) => {
    let immObj = Immutable.fromJS(result);

    _resultItem = _resultItem.push(immObj);
}

const _clearResult = () => {
    _resultItem = _resultItem.clear();
}

const ResultStore = Object.assign(EventEmitter.prototype, {
    emitChange (){
        this.emit( CHANGE_EVENT );
    },
    addChangeListener (callback) {
        this.on(CHANGE_EVENT, callback);
    },
    removeChangeListener (callback) {
        this.removeListener(CHANGE_EVENT, callback);
    },
    getResultItem () {
        return _resultItem;
    },
    dispatcherIndex: register(function (action) {
        switch (action.actionType) {
            case AppConstants.ADD_RESULT:
                _addResult(action.result);
            break;
            case AppConstants.CLEAR_RESULT:
                _clearResult();
            break;
        }

        ResultStore.emitChange();
    })
});

However, the page will become very slow after rendering more than 1000 datas. How to enhance the performance for rendering? I need to execute the linux script persistently more than 3 days. Any solutions? Thanks~


Solution

  • Is there any need to render all the data on screen? If not then there are a few ways to deal with cutting down the amount of visible data.

    Filter / Search

    You can provide a search/filter component that complements the list and creates a predicate function that can be used to determine whether each item should or should not be rendered.

    <PredicateList>
      <Search />
      <Filter />
      {this.state.result
         .filter(predicate)
         .map(function(item, index) {
           return <ListResultItem key={index} result={item} />;
         })
      }
    </PredicateList>
    

    Lazy Load

    Load the items only when they are asked for. You can work out whether item is needed by keeping track of whether it would be onscreen, or whether the mouse was over it.

    var Lazy = React.createClass({
      getInitialState: function() {
        return { loaded: false };
      },
      load: function() {
        this.setState({ loaded: true });
      },
      render: function() {
        var loaded = this.state.loaded,
            component = this.props.children,
            lazyContainer = <div onMouseEnter={this.load} />;
    
        return loaded ?
          component
          lazyContainer;
      }
    });
    

    Then simply wrap your data items inside these Lazy wrappers to have them render when they are requested.

    <Lazy>
      <ListResultItem key={index} result={item} />
    </Lazy>
    

    This ensures that only data needed by the user is seen. You could also improve the load trigger to work for more complex scenarios, such as when the component has been onscreen for more then 2 seconds.

    Pagination

    Finally, the last and most tried and tested approach is pagination. Choose a limit for a number of data items that can be shown in one go, then allow users to navigate through the data set in chunks.

    var Paginate = React.createClass({
      getDefaultProps: function() {
        return { items: [], perPage: 100 }; 
      },
      getInitialState: function() {
        return { page: 0 };
      },
      next: function() {
        this.setState({ page: this.state.page + 1});
      },
      prev: function() {
        this.setState({ page: this.state.page - 1});
      },
      render: function() {
        var perPage = this.props.perPage,
            currentPage = this.state.page,
            itemCount = this.props.items.length;
    
        var start = currentPage * perPage,
            end = Math.min(itemCount, start + perPage);
    
        var selectedItems = this.props.items.slice(start, end);
    
        return (
          <div className='pagination'>
            {selectedItems.map(function(item, index) {
              <ListResultItem key={index} result={item} />
            })}
            <a onClick={this.prev}>Previous {{this.state.perPage}} items</a>
            <a onClick={this.next}>Next {{this.state.perPage}} items</a>
          </div>
        );
      }
    });
    

    These are just very rough examples of implementations for managing the rendering of large amounts of data in efficient ways, but hopefully they will make enough sense for you to implement your own solution.