如何使用 React + Flux 快速渲染 >10000 个项目?

How to fast render >10000 items using React + Flux?

我想问一下在 React 中快速渲染 > 10000 项的正确方法是什么。

假设我想创建一个复选框列表,其中 包含超过 10000 个动态复选框项目

我创建了一个包含所有项目的商店,它将用作复选框列表的状态。

当我点击任何复选框项目时,它会通过操作更新相应的项目,因此商店发生变化。

由于商店已更改,因此会触发复选框列表更新。

复选框列表更新其状态并再次呈现。

这里的问题是,如果我单击任何复选框项目,我必须等待 > 3 秒才能看到复选框被选中。我不希望这是因为只有 1 个复选框项目需要重新呈现。

我试图找到根本原因。 最耗时的部分在checkbox list render方法里面,和.map有关,创建Checkbox组件形成componentList..但实际上只有1个checkbox需要重新渲染。

以下是我的代码。 我使用 ReFlux 作为通量架构。

CheckboxListStore

商店将所有复选框项目存储为地图。 (名称作为键,状态 (true/false) 作为值)

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

操作,创建、选中和取消选中提供名称的任何复选框项目。

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

复选框列表

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;

复选框项 在 onChange 回调中,我调用操作来更新项目。

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;

您的渲染函数看起来比它需要的要复杂一些:

  • 它首先生成一个 JSX 组件数组
  • 然后转换应用 (jQuery?) .toArray()
  • 然后returns这个新生成的数组。

也许将您的渲染函数简化为这样的东西会有所帮助?

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>
  );
},

您可以采取以下几种方法:

  1. 不要渲染所有 10,000 个 - 只渲染基于面板大小和滚动位置的可见复选框(+几个),并处理滚动事件以更新可见子集(为此使用组件状态,而不是通量)。您需要以某种方式处理滚动条,通过手动渲染滚动条,或者通过在顶部和底部添加巨大的空 div 来替换您未渲染的复选框,使用普通浏览器滚动条更容易,这样滚动条位于正确的位置。这种方法允许您处理 100,000 个甚至一百万个复选框,并且第一次渲染和更新都很快。可能是首选的解决方案。这里有很多这种方法的例子:http://react.rocks/tag/InfiniteScroll
  2. 微优化 - 你可以做 storage.toArray().map(...)(这样你就不会创建中间映射),或者更好的是,创建并清空数组然后做 storage.forEach(... ) 用 push 添加元素 - 快得多。但是 React diffing 算法仍然需要对 10000 个元素进行 diff,这永远不会很快,无论你编写生成元素的代码有多快。
  3. 以某种方式将你的大地图分成块,这样当你选中一个复选框时只有 1 个块发生变化。还以相同的方式(分成 CheckboxListChunks)或类似的方式拆分 React 组件。这样,只要每个块都有一个 PureComponent 类型的 componentShouldUpdate 函数,您就只需要重新渲染更改的块(可能 Reflux 会为您做这个?)。
  4. 远离基于 ImmutableJS 的通量,这样您就可以更好地控制什么时候发生变化(例如,您不必仅仅因为其中一个子项发生了变化而更新父复选框映射)。
  5. 将自定义的 shouldComponentUpdate 添加到 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());
    }
    

您真的需要每次check/uncheck都在您的存储中保存支票状态吗?

我最近遇到了和你一样的问题。只需在 CheckboxList 组件的状态中保存一个 checkedList 数组 [name1,name2 ...],并在每次 check/uncheck 一个项目时更改此 checkedList。当您想将检查状态保存到数据存储时,调用一个 Action.save() 并将 checkedList 传递给 store.
但是如果你真的需要每次都保存到数据存储check/uncheck,这个解决方案就无济于事了-_-。

顺便说一句,我放弃助焊剂... 我最终决定使用 mobservable 来解决我的问题。 我做了一个例子https://github.com/raymondsze/react-example 参见 https://github.com/raymondsze/react-example/tree/master/src/mobservable 的编码。

除了初始渲染之外,您还可以使用 Mobservable 显着提高大型集合的渲染速度。它通过自动应用横向加载避免在子项更改时不必要地重新渲染映射 10.000 个项目的父组件。请参阅此 blog 以获得深入的解释。