时间:2015-10-05 22:01:27

标签: javascript reactjs

我有一个非常大的动态表单,它是根据通过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理念,但我觉得它几乎立刻就出错了,因为这是我第一件事。我试图超越教程。

1 个答案:

答案 0 :(得分: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