jQuery UI Sortable与React.js不兼容

6
我在React中有一个可以排序的列表,使用了jQuery UI进行支持。当我拖放列表项时,我希望更新数组,以便存储列表的新顺序。然后使用更新后的数组重新渲染页面。即:this.setState({data: _todoList}); 目前拖拽列表项时,jQuery UI DnD有效,但是在UI中,列表项的位置不会改变,即使页面使用更新后的数组重新渲染。也就是说,在UI中,列表项仍然保持在列表中原来的位置,尽管定义其位置的数组已经成功更新。
如果您将该项拖动两次,则它将移动到正确的位置。
    // Enable jQuery UI Sortable functionality
    $(function() {
      $('.bank-entries').sortable({
        axis: "y",
        containment: "parent",
        tolerance: "pointer",
        revert: 150,
        start: function (event, ui) {
            ui.item.indexAtStart = ui.item.index();
        },
        stop: function (event, ui) {
            var data = {
                indexStart: ui.item.indexAtStart,
                indexStop: ui.item.index(),
                accountType: "bank"
            };
            AppActions.sortIndexes(data);
        },
      });
    });

    // This is the array that holds the positions of the list items
    var _todoItems = {bank: []};

    var AppStore = assign({}, EventEmitter.prototype, {
      getTodoItems: function() {
        return _todoItems;
      },
      emitChange: function(change) {
        this.emit(change);
      },
      addChangeListener: function(callback) {
        this.on(AppConstants.CHANGE_EVENT, callback);
      },
      sortTodo: function(todo) {
        // Dynamically choose which Account to target
        targetClass = '.' + todo.accountType + '-entries';

        // Define the account type
        var accountType = todo.accountType;

        // Loop through the list in the UI and update the arrayIndexes
        // of items that have been dragged and dropped to a new location
        // newIndex is 0-based, but arrayIndex isn't, hence the crazy math
        $(targetClass).children('form').each(function(newIndex) {
          var arrayIndex = Number($(this).attr('data-array-index'));
          if (newIndex + 1 !== arrayIndex) {
            // Update the arrayIndex of the element
            _todoItems[accountType][arrayIndex-1].accountData.arrayIndex = newIndex + 1;
          }
        });

        // Sort the array so that updated array items move to their correct positions
        _todoItems[accountType].sort(function(a, b){
          if (a.accountData.arrayIndex > b.accountData.arrayIndex) {
            return 1;
          }
          if (a.accountData.arrayIndex < b.accountData.arrayIndex) {
            return -1;
          }
          // a must be equal to b
          return 0;
        });

        // Fire an event that re-renders the UI with the new array
        AppStore.emitChange(AppConstants.CHANGE_EVENT);
      },
    }


  function getAccounts() {
    return { data: AppStore.getTodoItems() }
  }

  var Account = React.createClass({
      getInitialState: function(){
          return getAccounts();
      },
      componentWillMount: function(){
          AppStore.addChangeListener(this._onChange);

          // Fires action that triggers the initial load
          AppActions.loadComponentData();
      },
      _onChange: function() {
          console.log('change event fired');
          this.setState(getAccounts());
      },
      render: function(){
          return (
              <div className="component-wrapper">
                  <Bank data={this.state.data} />
              </div>
          )
      }
  });

这是一个可能有帮助的示例:https://gist.github.com/petehunt/7882164 - Mike Driver
感谢您的建议@MikeDriver,但是这行代码让我有些犹豫:“关键要注意的是我们让render()方法什么也不做”。我正在尝试利用render方法来保持React/Flux架构的一致性。 - Ben
2
我认为,如果你偏离了React架构,使用jQuery插件代替React本身具有的等效功能,那么就必须做出妥协。我并不是说你不能在React中使用jQuery插件 - 显然有些情况下这是唯一实际可行的解决方案,但是在这种情况下试图保持“Reactish”的风格有点晚了,我的看法是为时已晚。 - Mike Driver
3个回答

12

关键是在可排序 Sortable 的 stop 事件中调用 sortable('cancel'),然后让 React 更新 DOM。

componentDidMount() {
    this.domItems = jQuery(React.findDOMNode(this.refs["items"]))
    this.domItems.sortable({
        stop: (event, ui) => {
            // get the array of new index (http://api.jqueryui.com/sortable/#method-toArray)
            const reorderedIndexes = this.domItems.sortable('toArray', {attribute: 'data-sortable'}) 
            // cancel the sort so the DOM is untouched
            this.domItems.sortable('cancel')
            // Update the store and let React update (here, using Flux)
            Actions.updateItems(Immutable.List(reorderedIndexes.map( idx => this.state.items.get(Number(idx)))))
        }
    })
}

1
谢谢!这是我目前为止找到的最简单的解决方法。 - Michael Yin
是的!“cancel”是唯一有效的选项——它比实现一个“Sortable”ReactJS组件更好。 - Ross The Boss

6
jQuery UI Sortable无法与React一起使用的原因是因为它直接改变DOM,这在React中是绝对不允许的。
要使其正常工作,您需要修改jQuery UI Sortable,以保留DnD功能,但在放置元素时不修改DOM。相反,它可以触发一个事件,该事件会使用元素的新位置触发React渲染。

2

由于React使用虚拟DOM,您需要使用函数React.findDOMNode()来访问实际的DOM元素。

我建议在组件的componentDidMount方法中调用jQuery UI函数,因为您的元素必须已经渲染才能访问。

// You have to add a ref attribute to the element with the '.bank-entries' class
$( React.findDOMNode( this.refs.bank_entries_ref ) ).sortable( /.../ );

文档 - 与浏览器一起工作(这里包含您需要了解的所有内容)

希望这样可以让您理解并解决您的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接