【问题标题】:React js rerender of large number of components in a formReact js重新渲染表单中的大量组件
【发布时间】:2015-10-05 22:01:26
【问题描述】:

我有一个非常大的动态表单,它根据通过 AJAX 收集的数据呈现。

有一个包含表格的表格。该表包含一个标题,然后是一系列行(由状态数据确定)。然后每一行都有一系列单元格(也由状态数据决定)。每个单元格都包含一个复选框。

我的问题是关于在哪里维护大量复选框的状态。最初我的方法是让复选框控制组件,每个单元格都保持其复选框的状态。

我还尝试将选择框的状态实现为父表单组件本身的状态。

前一种方法的问题在于,当我提交表单时,我没有表单级别的状态,因为它隐藏在单元格后代中。我能以某种方式获得这种状态吗? findDomNode 似乎只是当前组件,而不是级联。此外,报头单元格在选中列中的所有行时,都会检查复选框,此解决方案不会使我能够更新该信息。

后一种方法的问题是,当我对一个复选框进行更改时,状态更改会导致表单组件重新渲染,进而导致整个表格重新渲染(或至少调用它的渲染方法)。这非常耗时,而且对于一个大表(通常可以是 100x50)需要非常不友好的时间来重新渲染。

归根结底,选中复选框的影响应该很小。复选框本身应该更新,如果列的全选状态发生变化,可能需要更新列标题以选中/取消选中。就是这样。

我已经删除了很多代码,但这基本上是我正在使用的。知道表格中的单元格复选框会跟踪应将哪些标签应用于图像可能会有所帮助。表头单元跟踪哪些标记(从图像名称中提取的文本)映射到哪些标记。当这种情况发生变化时,必须重新呈现该列(另一个我尚未解决的问题,我怀疑如果不完全重新呈现所有内容,这将再次变得困难)。

var AutoTagForm = React.createClass({
  getInitialState: function() {
    return {data: {
        tags: [],
        images: {},
        tokens: [],
        tokenTagMap: {}
      }
    };
  },
  componentDidMount: function() {
    $.ajax({
      url: this.props.url,
      type: "POST",
      data: { imageIds: this.props.images },
      dataType: 'json',
      cache: false,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },

  onSubmit: function(e) {
    e.preventDefault()

    // Submit the updates to the server
  },

  cellCheckedChange: function(image, tag) {
    // If the image has the tag, add it. Otherwise remove it

    var tagIndex = image.checkedTags.indexOf(tag)

    // TODO Perhaps it is safer to find the image object in this.state.data?
    if (tagIndex === -1) {
      image.checkedTags.push(tag);
    } else {
      image.checkedTags.splice(tagIndex, 1);
    }

    this.setState({
      data: this.state.data
    })

  },

  render: function() {
    var images = this.state.data.images;
    var tokenTagMap = this.state.data.tokenTagMap;
    var cellCheckedChange = this.cellCheckedChange;

    var rowNodes = Object.keys(images).map(function(key){
      if (images.hasOwnProperty(key)) {
        var image = images[key];

        return (
          <AutoTagImageRow key={image.id}
                           image={image}
                           tokenTagMap={tokenTagMap}
                           cellCheckedChange={cellCheckedChange} />
        );
      }
    });

    return (
      <form ref="form" onSubmit={this.onSubmit} id="updateAllForm" className={'autotagForm'}>
        <div>
          <input type="submit"
                 value="Apply" />
        </div>

        <div>
          <table>
            <thead>
              <AutoTagHeaderRow tags={this.state.data.tags}
                                tokens={this.state.data.tokens}
                                tokenTagMap={this.state.data.tokenTagMap} />
            </thead>

            <tbody>
              {rowNodes}
            </tbody>

          </table>
            </div>
        </form>
    );
  }
});


var AutoTagImageRow = React.createClass({
  render: function() {
    var image = this.props.image;
    var tokenTagMap = this.props.tokenTagMap;
    var cellCheckedChange = this.props.cellCheckedChange;

    var cellNodes = Object.keys(tokenTagMap).map(function(token){
      if (tokenTagMap.hasOwnProperty(token)) {
        return (
          <AutoTagImageRowCell image={image}
                               token={token}
                               tokenTagMap={tokenTagMap}
                               cellCheckedChange={cellCheckedChange} />
        );
      }
    });

    return (
      <tr>
        {cellNodes}
        <td>{image.clientPath}</td>
      </tr>
    );
  }
});


var AutoTagImageRowCell = React.createClass({

  isTagged: function() {
    var image = this.props.image;
    var token = this.props.token;
    var tokenTagMap = this.props.tokenTagMap;
    var tag = tokenTagMap[token];

    return (image.tags.indexOf(tag) !== -1);
  },

  isChecked: function() {
    var image = this.props.image;
    var token = this.props.token;
    var tokenTagMap = this.props.tokenTagMap;
    var tag = tokenTagMap[token];

    return (image.checkedTags.indexOf(tag) !== -1);
  },

  handleCheckedChange: function() {
    var image = this.props.image;
    var token = this.props.token;
    var tokenTagMap = this.props.tokenTagMap;
    var tag = tokenTagMap[token];

    this.props.cellCheckedChange(image, tag);
  },

  render: function() {

    var className = '';
    if (this.isTagged()) {
      className = 'success';
    }

    return (
      <td className={className}>
        <input type="checkbox"
               checked={this.isChecked()}
               onChange={this.handleCheckedChange} />
      </td>
    );
  }
});

我是 React 新手,可能会做出一些糟糕的设计决定!这与我之前使用简单的 javascript 做事的方式非常不同,我热衷于遵循 React 哲学,但我觉得它几乎立刻就出错了,因为这是我尝试做的第一件事通过教程。

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    好的。经过一些额外的研究,我找到了一个很好的解决这个问题的方法。

    关键是能够判断哪些组件需要重新渲染。这是我之前考虑过的,但是在我的状态下确定是否需要重新渲染图像行所需的比较几乎与重新渲染本身一样密集。

    输入Immutable.js。我把我的 javascript 对象变成了不可变的映射,把我的数组变成了不可变的集合。像这样:

        for (var key in data.images) {
          // data.images[key] = Immutable.fromJS(data.images[key]);
    
          data.images[key] = Immutable.fromJS(
            data.images[key], function (key, value) {
              var isIndexed = Immutable.Iterable.isIndexed(value);
              return isIndexed ? value.toSet() : value.toMap();
            }
          );
        }
    

    我还没有转换整个 data,因为那里还有其他数据位尚未更新为使用不可变,但这是可能的。

    然后,当我从其中一个复选框处理 onChange 时,我会在表单级别实现它,如下所示:

      cellCheckedChange: function(imageId, tagId) {
        var images = this.state.data.images;
        var image = images[imageId];
    
        // If the image has the tag, add it. Otherwise remove it
        var checked = image.hasIn(['checkedTags', tagId]);
    
        images[imageId] = image.update('checkedTags', function(value) {
          return checked ? value.delete(tagId): value.add(tagId);
        });
    
        this.setState({
          data: this.state.data
        });
    
      },
    

    因此,对不可变对象的更新会导致创建一个新对象。这将对不可变状态结构产生向上的级联效应,因为对子节点的任何更改都需要新的父节点。但是,任何未直接更新或未通过此向上级联更新的状态仍然是相同的对象,这很重要,因为您可以独立地推理这些对象。

    最后,我更新了我的图像行组件,仅在图像是新对象时才更新(如果图像或任何后代已更新):

    var AutoTagImageRow = React.createClass({
      render: function() {
        ...
      },
      // Only update an image row that has had a modified state
      shouldComponentUpdate: function(nextProps, nextState) {
        return nextProps.image !== this.props.image;
      }
      ...
    });
    

    因此,当更新发生时,会发生更多的工作来操纵 javascript 状态,但作为回报,我可以很容易地推断出哪些发生了变化,哪些没有发生变化!

    正如我上面提到的,不可变对象的嵌套层次结构中任何未被替换的部分都是相同的对象,因此可以将它们与旧结构的子对象进行比较,以查看它们是否已用结果更新他们没有。这是一个独立的示例。

    var Immutable = require('immutable');
    
    var input = {
      '1': 'aaa',
      '2': [1,2,3,4]
    };
    
    var x = Immutable.fromJS(
      input, function (key, value) {
        var isIndexed = Immutable.Iterable.isIndexed(value);
        return isIndexed ? value.toSet() : value.toMap();
      }
    );
    
    // Get another reference to x
    var y = x;
    
    // Update the array stored in '2'
    x = x.update('2', function(v) {
        return v.add(7);
    });
    
    // object '1' remains the same JS object
    console.log(x.get('1') === y.get('1')); //True
    
    // object '2' is a new JS object
    console.log(x.get('2') === y.get('2')); //False
    
    // Overall 'x' is a new JS object
    console.log(x === y); //False
    

    【讨论】:

      猜你喜欢
      • 2016-01-11
      • 2021-12-29
      • 2021-06-19
      • 2018-04-22
      • 1970-01-01
      • 2021-06-07
      • 2020-08-18
      • 2020-03-22
      相关资源
      最近更新 更多