如何在React JS中使用jQuery UI

时间:2016-08-08 18:55:21

标签: jquery-ui reactjs

如何在React中使用jQuery UI?我看过Googling的一些例子,但所有这些例子似乎都已经过时了。

5 个答案:

答案 0 :(得分:75)

如果真的需要这样做,这是我正在使用的方法。

计划:创建一个组件来管理jQuery插件。该组件将提供jQuery组件的以React为中心的视图。而且,它会:

  • 使用React生命周期方法初始化和拆除jQuery插件;
  • 使用React props作为插件配置选项并连接到插件的方法事件;
  • 卸载组件时销毁该插件。

让我们通过jQuery UI Sortable插件探索一个如何做到这一点的实际示例。

TLDR:最终版本

如果您只想获取包装的jQuery UI可排序示例的最终版本:

...另外,以下缩短了较长的评论代码段:

class Sortable extends React.Component {
    componentDidMount() {
        this.$node = $(this.refs.sortable);
        this.$node.sortable({
            opacity: this.props.opacity,
            change: (event, ui) => this.props.onChange(event, ui)
        });
    }

    shouldComponentUpdate() { return false; }

    componentWillReceiveProps(nextProps) {
        if (nextProps.enable !== this.props.enable)
            this.$node.sortable(nextProps.enable ? 'enable' : 'disable');
    }

    renderItems() {
        return this.props.data.map( (item, i) =>
            <li key={i} className="ui-state-default">
                <span className="ui-icon ui-icon-arrowthick-2-n-s"></span>
                { item }
            </li>
        );
    }
    render() {
        return (
            <ul ref="sortable">
                { this.renderItems() }
            </ul>
        );
    }

    componentWillUnmount() {
        this.$node.sortable('destroy');
    }
};

或者,您可以设置默认道具(在没有传递的情况下)和道具类型:

Sortable.defaultProps = {
    opacity: 1,
    enable: true
};

Sortable.propTypes = {
    opacity: React.PropTypes.number,
    enable: React.PropTypes.bool,
    onChange: React.PropTypes.func.isRequired
};

...以及如何使用<Sortable />组件:

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        // Use this flag to disable/enable the <Sortable />
        this.state = { isEnabled: true };

        this.toggleEnableability = this.toggleEnableability.bind(this);
    }

    toggleEnableability() {
        this.setState({ isEnabled: ! this.state.isEnabled });
    }

    handleOnChange(event, ui) {
        console.log('DOM changed!', event, ui);
    }

    render() {
        const list = ['ReactJS', 'JSX', 'JavaScript', 'jQuery', 'jQuery UI'];

        return (
            <div>
                <button type="button"
                    onClick={this.toggleEnableability}>
                    Toggle enable/disable
                </button>
                <Sortable
                    opacity={0.8}
                    data={list}
                    enable={this.state.isEnabled}
                    onChange={this.handleOnChange} />
            </div>
        );
    }
}

ReactDOM.render(<MyComponent />, document.getElementById('app'));

完整解释

对于那些想要了解为什么如何的人。这是一步一步的指南:

步骤1:创建组件。

我们的组件将接受项目(字符串)的数组(列表)为data prop。

class Sortable extends React.Component {
    componentDidMount() {
        // Every React component has a function that exposes the
        // underlying DOM node that it is wrapping. We can use that
        // DOM node, pass it to jQuery and initialize the plugin.

        // You'll find that many jQuery plugins follow this same pattern
        // and you'll be able to pass the component DOM node to jQuery
        // and call the plugin function.

        // Get the DOM node and store the jQuery element reference
        this.$node = $(this.refs.sortable);

        // Initialize the jQuery UI functionality you need
        // in this case, the Sortable: https://jqueryui.com/sortable/
        this.$node.sortable();
    }

    // jQuery UI sortable expects a <ul> list with <li>s.
    renderItems() {
        return this.props.data.map( (item, i) =>
            <li key={i} className="ui-state-default">
                <span className="ui-icon ui-icon-arrowthick-2-n-s"></span>
                { item }
            </li>
        );
    }
    render() {
        return (
            <ul ref="sortable">
                { this.renderItems() }
            </ul>
        );
    }
};

第2步:通过道具传递配置选项

我们想说我们要配置the opacity of the helper while sorting。我们将在插件配置中使用opacity选项,该值从0.011

class Sortable extends React.Component {
    // ... omitted for brevity

    componentDidMount() {
        this.$node = $(this.refs.sortable);

        this.$node.sortable({
            // Get the incoming `opacity` prop and use it in the plugin configuration
            opacity: this.props.opacity,
        });
    }

    // ... omitted for brevity
};

// Optional: set the default props, in case none are passed
Sortable.defaultProps = {
    opacity: 1
};

现在我们可以在代码中使用该组件:

<Sortable opacity={0.8} />

同样,我们可以映射任何jQUery UI Sortable options

第3步:插件事件的连接功能。

你很可能需要连接一些插件方法,以便执行一些React逻辑,例如,操纵状态让我们这一天。

以下是如何做到这一点:

class Sortable extends React.Component {
    // ... omitted for brevity

    componentDidMount() {
        this.$node = $(this.refs.sortable);

        this.$node.sortable({
            opacity: this.props.opacity,
            // Get the incoming onChange function
            // and invoke it on the Sortable `change` event
            change: (event, ui) => this.props.onChange(event, ui)
        });
    }

    // ... omitted for brevity
};

// Optional: set the prop types
Sortable.propTypes = {
    onChange: React.PropTypes.func.isRequired
};

以下是如何使用它:

<Sortable
    opacity={0.8}
    onChange={ (event, ui) => console.log('DOM changed!', event, ui) } />

步骤4:将未来的更新控制传递给jQuery

在ReactJS在实际DOM中添加元素之后,我们需要将未来的控制传递给jQuery。否则,ReactJS永远不会重新渲染我们的组件,但我们不希望这样。我们希望jQuery负责所有更新。

React生命周期方法得到了拯救!

使用shouldComponentUpdate()让React知道组件的输出是否不受当前状态或道具更改的影响。默认行为是在每次状态更改时重新呈现,在绝大多数情况下,但我们不想要这种行为!

在收到新的道具或州时,在渲染之前调用

shouldComponentUpdate()。如果shouldComponentUpdate()返回false,则不会调用componentWillUpdate()render()componentDidUpdate()

然后,我们使用componentWillReceiveProps(),我们将this.propsnextProps进行比较,并仅在必要时调用jQuery UI可排序更新。对于此示例,我们将实现jQuery UI Sortable的启用/禁用选项。

class Sortable extends React.Component {
    // Force a single-render of the component,
    // by returning false from shouldComponentUpdate ReactJS lifecycle hook.
    // Right after ReactJS adds the element in the actual DOM,
    // we need to pass the future control to jQuery.
    // This way, ReactJS will never re-render our component,
    // and jQuery will be responsible for all updates.
    shouldComponentUpdate() {
        return false;
    }

    componentWillReceiveProps(nextProps) {
        // Each time when component receives new props,
        // we should trigger refresh or perform anything else we need.
        // For this example, we'll update only the enable/disable option,
        // as soon as we receive a different value for this.props.enable
        if (nextProps.enable !== this.props.enable) {
            this.$node.sortable(nextProps.enable ? 'enable' : 'disable');
        }
    }

    // ... omitted for brevity
};

// Optional: set the default props, in case none are passed
Sortable.defaultProps = {
    enable: true
};

// Optional: set the prop types
Sortable.propTypes = {
    enable: React.PropTypes.bool
};

第5步:清理混乱。

许多jQuery插件提供了一种在不再需要时自行清理的机制。 jQuery UI Sortable提供了一个事件,我们可以触发它来告诉插件取消绑定其DOM事件并销毁。 React生命周期方法再次得到拯救,并提供了一种挂钩组件的机制。

class Sortable extends React.Component {
    // ... omitted for brevity

    componentWillUnmount() {
        // Clean up the mess when the component unmounts
        this.$node.sortable('destroy');
    }

    // ... omitted for brevity
};

结论

使用React包装jQuery插件并不总是最佳选择。但是,很高兴知道它是一个选项以及如何实现解决方案。如果您要将遗留的jQuery应用程序迁移到React,或者您根本无法找到适合您需要的React插件,那么这是一个可行的选择。

如果库修改了DOM,我们会尝试让React远离。当React完全控制DOM时,React效果最佳。在这些情况下,React组件更像是第三方库的包装器。主要通过使用componentDidMount / componentWillUnmount来初始化/销毁第三方库。而道具作为一种方式,为父母提供了一种方法,可以自定义孩子包装的第三方库的行为,并与插件事件挂钩。

您可以使用此方法集成几乎所有jQuery插件

答案 1 :(得分:12)

React并不适用于直接进行DOM突变的文库。如果其他东西改变了React试图渲染的DOM,它将抛出错误。如果你来完成这项工作,你最好的妥协是让你的页面的不同部分由不同的东西管理,例如包含你的jquery组件的div,然后是其他一些div包含您的React组件。然而,在这些不同的(jquery和反应)组件之间进行通信将是困难的,而且说实话,选择其中一个可能更好。

答案 2 :(得分:7)

虽然技术上完美无缺,但Kayolan的答案有一个致命的缺陷,恕我直言:在将未来UI更新的责任从React传递给jQuery时,他反而忽略了React首先出现在那里的观点! React控制可排序列表的初始呈现,但在此之后,一旦用户执行第一次jQueryUI拖动/排序操作,React的状态数据就会过时。 React的重点是在视图级别表示您的状态数据。

所以,当我解决这个问题时,我采取了相反的方法:我试图确保React尽可能地控制。我不让jQueryUI Sortable控件完全改变DOM

怎么可能?好吧jQuery-ui的sortable()方法有一个cancel调用,它将UI设置回你开始拖放之前的状态。诀窍是在发出cancel调用之前读取可排序控件的状态。这样,我们可以在cancel调用将DOM设置回原来的状态之前,获取用户的意图的内容。一旦我们有了这些意图,我们就可以将它们传递给React,并将状态数据操作为用户想要的新顺序。最后,在该数据上调用setState()以使React呈现新的顺序。

我是这样做的:

  1. 将jquery-ui.sortable()方法附加到订单项列表中(当然是由React生成的!)
  2. 让用户将这些订单项拖放到DOM周围。
  3. 当用户开始拖动时,我们会读取用户从拖动的订单项的索引。
  4. 当用户放弃订单项时,我们:
    1. 从jQuery-ui.sortable()中读取订单项的新索引位置,即列表中用户放弃的位置。
    2. cancel调用传递给jQuery-ui.sortable(),以使列表返回其原始位置,并且DOM保持不变。
    3. 将拖动的订单项的旧索引和新索引作为参数传递给React模块中的JavaScript函数。
    4. 让该功能将列表的后端状态数据重新排序为用户拖放的新顺序。
    5. 进行反响setState()来电。
  5. UI中的列表现在将反映我们的州数据的新顺序;这是标准的React功能。

    因此,我们可以使用jQueryUI Sortable的拖放功能,但是根本不需要更改DOM。 React很高兴,因为它控制着DOM(应该在哪里)。

    https://github.com/brownieboy/react-dragdrop-test-simple处的Github存储库示例。这包括现场演示的链接。

答案 3 :(得分:1)

关于Kaloyan Kosev's long answer,我必须为我想要使用的每个jQueryUi功能创建一个组件吗?不用了,谢谢!更改 DOM 时,为什么不简单地更新状态? Followig适合我:

export default class Editor extends React.Component {

    // ... constructor etc.

    componentDidMount() {
        this.initializeSortable();
    }

    initializeSortable() {
        const that = this;
        $('ul.sortable').sortable({
            stop: function (event, ui) {
                const usedListItem = ui.item;
                const list = usedListItem.parent().children();
                const orderedIds = [];
                $.each(list, function () {
                    orderedIds.push($(this).attr('id'));
                })
                that.orderSortableListsInState(orderedIds);
            }
        });
    }

    orderSortableListsInState(orderedIds) {

        // ... here you can sort the state of any list in your state tree

        const orderedDetachedAttributes = this.orderListByIds(orderedIds, this.state.detachedAttributes);
        if (orderedDetachedAttributes.length) {
            this.state.detachedAttributes = orderedDetachedAttributes;
        }
        this.setState(this.state);
    }

    orderListByIds(ids, list) {
        let orderedList = [];
        for (let i = 0; i < ids.length; i++) {
            let item = this.getItemById(ids[i], list);
            if (typeof item === 'undefined') {
                continue;
            }
            orderedList.push(item);
        }
        return orderedList;
    }

    getItemById(id, items) {
        return items.find(item => (item.id === id));
    }

    // ... render etc.

}

list元素只需要一个额外的属性让jQuery选择元素。

import React from 'react';

export default class Attributes extends React.Component {
    render() {
        const attributes = this.props.attributes.map((attribute, i) => {
           return (<li key={attribute.id} id={attribute.id}>{attribute.name}</li>);
        });

        return (
            <ul className="sortable">
                {attributes}
            </ul>
        );
    }
}

对于ID,我使用UUID,所以我在orderSortableListsInState()中匹配时没有冲突。

答案 4 :(得分:0)

我无法使jquery-ui npm包正常工作。对我有用的是使用jquery-ui-bundle:

import $ from 'jquery';
import 'jquery-ui-bundle';
import 'jquery-ui-bundle/jquery-ui.min.css';