最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to fast render >10000 items using React + Flux? - Stack Overflow

programmeradmin0浏览0评论

I would like to ask what is the correct way to fast render > 10000 items in React.

Suppose I want to make a checkboxList which contain over dynamic 10000 checkbox items.

I make a store which contain all the items and it will be used as state of checkbox list.

When I click on any checkbox item, it will update the corresponding item by action and so the store is changed.

Since store is changed so it trigger the checkbox list update.

The checkbox list update its state and render again.

The problem here is if I click on any checkbox item, I have to wait > 3 seconds to see the checkbox is ticked. I don't expect this as only 1 checkbox item need to be re-rendered.

I try to find the root cause. The most time-consuming part is inside the checkbox list render method, related to .map which create the Checkbox ponent to form ponentList.. But actually only 1 checkbox have to re-render.

The following is my codes. I use ReFlux for the flux architecture.

CheckboxListStore

The Store store all the checkbox item as map. (name as key, state (true/false) as value)

const Reflux = require('reflux');
const Immutable = require('immutable');
const checkboxListAction = require('./CheckboxListAction');

let storage = Immutable.OrderedMap();
const CheckboxListStore = Reflux.createStore({
	listenables: checkboxListAction,
	onCreate: function (name) {
		if (!storage.has(name)) {
			storage = storage.set(name, false);
			this.trigger(storage);
		}
	},
	onCheck: function (name) {
		if (storage.has(name)) {
			storage = storage.set(name, true);
			this.trigger(storage);
		}
	},
	onUncheck: function (name) {
		if (storage.has(name)) {
			storage = storage.set(name, false);
			this.trigger(storage);
		}
	},
	getStorage: function () {
		return storage;
	}
});

module.exports = CheckboxListStore;

I would like to ask what is the correct way to fast render > 10000 items in React.

Suppose I want to make a checkboxList which contain over dynamic 10000 checkbox items.

I make a store which contain all the items and it will be used as state of checkbox list.

When I click on any checkbox item, it will update the corresponding item by action and so the store is changed.

Since store is changed so it trigger the checkbox list update.

The checkbox list update its state and render again.

The problem here is if I click on any checkbox item, I have to wait > 3 seconds to see the checkbox is ticked. I don't expect this as only 1 checkbox item need to be re-rendered.

I try to find the root cause. The most time-consuming part is inside the checkbox list render method, related to .map which create the Checkbox ponent to form ponentList.. But actually only 1 checkbox have to re-render.

The following is my codes. I use ReFlux for the flux architecture.

CheckboxListStore

The Store store all the checkbox item as map. (name as key, state (true/false) as value)

const Reflux = require('reflux');
const Immutable = require('immutable');
const checkboxListAction = require('./CheckboxListAction');

let storage = Immutable.OrderedMap();
const CheckboxListStore = Reflux.createStore({
	listenables: checkboxListAction,
	onCreate: function (name) {
		if (!storage.has(name)) {
			storage = storage.set(name, false);
			this.trigger(storage);
		}
	},
	onCheck: function (name) {
		if (storage.has(name)) {
			storage = storage.set(name, true);
			this.trigger(storage);
		}
	},
	onUncheck: function (name) {
		if (storage.has(name)) {
			storage = storage.set(name, false);
			this.trigger(storage);
		}
	},
	getStorage: function () {
		return storage;
	}
});

module.exports = CheckboxListStore;

CheckboxListAction

The action, create, check and uncheck any checkbox item with name provided.

const Reflux = require('reflux');
const CheckboxListAction = Reflux.createActions([
	'create',
	'check',
	'uncheck'
]);
module.exports = CheckboxListAction;

CheckboxList

const React = require('react');
const Reflux = require('reflux');
const $ = require('jquery');
const CheckboxItem = require('./CheckboxItem');
const checkboxListAction = require('./CheckboxListAction');
const checkboxListStore = require('./CheckboxListStore');
const CheckboxList = React.createClass({
	mixins: [Reflux.listenTo(checkboxListStore, 'onStoreChange')],
	getInitialState: function () {
		return {
			storage: checkboxListStore.getStorage()
		};
	},
	render: function () {
		const {storage} = this.state;
		const LiComponents = storage.map((state, name) => {
			return (
				<li key = {name}>
					<CheckboxItem name = {name} />
				</li>
			);
		}).toArray();
		return (
			<div className = 'checkbox-list'>
				<div>
					CheckBox List
				</div>
				<ul>
					{LiComponents}
				</ul>
			</div>
		);
	},
	onStoreChange: function (storage) {
		this.setState({storage: storage});
	}
});

module.exports = CheckboxList;

CheckboxItem Inside onChange callback, I call the action to update the item.

const React = require('react');
const Reflux = require('reflux');
const $ = require('jquery');
const checkboxListAction = require('./CheckboxListAction');
const checkboxListStore = require('./CheckboxListStore');

const CheckboxItem = React.createClass({
	mixins: [Reflux.listenTo(checkboxListStore, 'onStoreChange')],
	propTypes: {
		name: React.PropTypes.string.isRequired
	},
	getInitialState: function () {
		const {name} = this.props;
		return {
			checked: checkboxListStore.getStorage().get(name)
		};
	},
	onStoreChange: function (storage) {
		const {name} = this.props;
		this.setState({
			checked: storage.get(name)
		});
	},
	render: function () {
		const {name} = this.props;
		const {checked} = this.state;
		return (
			<div className = 'checkbox' style = {{background: checked ? 'green' : 'white'}} >
				<span>{name}</span>
				<input ref = 'checkboxElement' type = 'checkbox'
					onChange = {this.handleChange}
					checked = {checked}/>
			</div>
		);
	},
	handleChange: function () {
		const {name} = this.props;
		const checked = $(this.refs.checkboxElement).is(':checked');
		if (checked) {
			checkboxListAction.check(name);
		} else {
			checkboxListAction.uncheck(name);
		}
	}
});

module.exports = CheckboxItem;

Share Improve this question asked Nov 27, 2015 at 14:40 RaymondFakeCRaymondFakeC 751 silver badge7 bronze badges 6
  • I just suggest a case. In my real work, I have 300 rows, each row contains 15 checkbox – RaymondFakeC Commented Nov 27, 2015 at 14:45
  • Consider implementing shouldComponentUpdate for each ponent – WickyNilliams Commented Nov 27, 2015 at 15:20
  • @WickyNilliams if the performance bottleneck is inside the .map of the 10,000 items (not in the render apparently), then shouldComponentUpdate for each item is not likely to help much in terms of performance. – wintvelt Commented Nov 27, 2015 at 16:13
  • But @WickyNilliams may have a very good point: where is your performance bottleneck? a) in creating the array of 10000? Or b) in rendering the created array of 10000? You could do simple console.log(Date.now()) before and after defining your LiComponents to check. – wintvelt Commented Nov 27, 2015 at 16:14
  • I ran into a similar problem before. The issue was that there were too many elements being displayed at one time. I solved it by hiding (using display: none or visibility: hidden) any elements that are outside the viewport. – user4639281 Commented Nov 27, 2015 at 20:24
 |  Show 1 more ment

5 Answers 5

Reset to default 2

There are a few approaches you can take:

  1. Don't render all 10,000 - just render the visible check boxes (+ a few more) based on panel size and scroll position, and handle scroll events to update the visible subset (use ponent state for this, rather than flux). You'll need to handle the scroll bar in some way, either by rendering one manually, or easier by using the normal browser scroll bar by adding huge empty divs at the top and bottom to replace the checkboxes you aren't rendering, so that the scroll bar sits at the correct position. This approach allows you to handle 100,000 checkboxes or even a million, and the first render is fast as well as updates. Probably the preferred solution. There are lots of examples of this kind of approach here: http://react.rocks/tag/InfiniteScroll
  2. Micro-optimize - you could do storage.toArray().map(...) (so that you aren't creating an intermediate map), or even better, make and empty array and then do storage.forEach(...) adding the elements with push - much faster. But the React diffing algorithm is still going to have to diff 10000 elements, which is never going to be fast, however fast you make the code that generates the elements.
  3. Split your huge Map into chunks in some way, so that only 1 chunk changes when you check a chechbox. Also split up the React ponents in the same way (into CheckboxListChunks) or similar. This way, you'll only need to re-render the changed chunk, as long as you have a PureComponent type ponentShouldUpdate function for each chunk (possibly Reflux does this for you?).
  4. Move away from ImmutableJS-based flux, so you have better control over what changes when (e.g. you don't have to update the parent checkbox map just because one of the children has changed).
  5. Add a custom shouldComponentUpdate to CheckboxList:

    shouldComponentUpdate:function(nextProps, nextState) {
        var storage = this.state.storage;
        var nextStorage = nextState.storage;
        if (storage.size !== nextStorage.size) return true;
        // check item names match for each index:
        return !storage.keySeq().equals(nextStorage.keySeq());
    }
    

Beyond the initial render, you can significantly increase rendering speed of large collections by using Mobservable. It avoids re-rendering the parent ponent that maps over the 10.000 items unnecessarily when a child changes by automatically applying side-ways loading. See this blog for an in-depth explanation.

Btw, I give up flux... I finally decided to use mobservable to solve my problem. I have made an example https://github./raymondsze/react-example see the https://github./raymondsze/react-example/tree/master/src/mobservable for the coding.

Your render function looks somewhat more plicated then it needs to be:

  • it first generates an array of JSX ponents
  • then converts applies a (jQuery?) .toArray()
  • then returns this newly generated array.

Maybe simplifying your render function to something like this would help?

render: function () {
  return (
    <div className = 'checkbox-list'>
      <div>
        CheckBox List
      </div>
      <ul>
        {this.state.storage.map((state, name) => {
          return (
            <li key = {name}>
              <CheckboxItem name = {name} />
            </li>
          );
         })}
      </ul>
    </div>
  );
},

Do you really need to save the check status in you store every time check/uncheck?

I recently meet similar problem like you. Just save a checkedList array [name1,name2 ...] in the CheckboxList ponent's state, and change this checkedList every time you check/uncheck an item. When you want to save check status to data storage, call an Action.save() and pass the checkedList to store.
But if you really need to save to data storage every time check/uncheck, this solution won't help -_-.

发布评论

评论列表(0)

  1. 暂无评论