jquery-ui - theme - tooltip jquery




如何在React JS中使用jQuery UI (4)

如何在React中使用jQuery UI? 我在Googling看過幾個例子,但所有這些例子似乎都已經過時了。


React不能很好地發揮直接DOM突變的庫。 如果其他東西改變了React試圖渲染的DOM,它將拋出錯誤。 如果你 必須 使這項工作,你最好的妥協是讓你的頁面的不同部分由不同的東西管理,例如包含你的jquery組件的div,然後是包含你的React組件的其他div( S)。 然而,在這些不同的(jquery和react)組件之間進行通信將是困難的,而且老實說,選擇其中一個可能更好。


如果你 真的 需要這樣做,這是我正在使用的方法。

計劃: 創建一個組件來管理jQuery插件 。 該組件將提供jQuery組件的以React為中心的視圖。 而且,它將:

  • 使用React生命週期方法初始化和拆除jQuery插件;
  • 使用React props 作為插件配置選項並連接到插件的方法事件;
  • 組件卸載時銷毀插件。

讓我們通過 jQuery UI Sortable 插件探索一個如何實現這一目的的實際示例。

TLDR:最終版本

如果您只想獲取包裝的jQuery UI Sortable示例的最終版本:

...另外,下面是 較長的評論 代碼片段 縮短了

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 支柱。

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步:通過道具傳遞配置選項

假設我們想要 在排序時 配置 幫助程序的不透明度 。 我們將在插件配置中使用 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選項

第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的enable / disable選項。

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

第五步:清理亂七八糟的東西。

許多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插件


關於 Kaloyan Kosev的長篇答案 ,我必須為我想要使用的每個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>
        );
    }
}

對於ids,我使用 UUID ,所以我在 orderSortableListsInState() 匹配時沒有衝突。


雖然技術上完美無缺,但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. 進行React setState() 調用。

UI中的列表現在將反映我們的狀態數據的新順序; 這是標準的React功能。

因此,我們可以使用jQueryUI Sortable的拖放功能,但是根本不需要更改DOM。 React很高興,因為它控制著DOM(它應該在哪裡)。

Github存儲庫示例位於 https://github.com/brownieboy/react-dragdrop-test-simple 。 這包括現場演示的鏈接。





reactjs