如何在 React 中使用 jQuery UI?我看过谷歌搜索的几个例子,但它们似乎都已经过时了。
如何在 React JS 中使用 jQuery UI
如果你真的需要这样做,这是我正在使用的一种方法。
计划:创建一个组件来管理 jQuery 插件。该组件将提供 jQuery 组件的以 React 为中心的视图。此外,它将:
- 使用 React 生命周期方法初始化和拆除 jQuery 插件;
- 使用 React
props
作为插件配置选项并连接到插件的方法事件; - 在组件卸载时销毁插件。
让我们探索一个实际示例,如何使用jQuery UI Sortable插件来做到这一点。
TLDR:最终版本
如果您只想获取包装好的 jQuery UI Sortable 示例的最终版本:
- 这是我制作的带有完整注释的GIST;
- 这是一个jsfiddle DEMO,还有完整的注释评论;
...另外,以下是较长注释代码片段的缩短:
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');
}
};
或者,您可以设置默认props(在没有传递的情况下)和props类型:
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
props。
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 步:通过 props 传递配置选项
假设我们要在排序时配置助手的不透明度。我们将使用opacity
插件配置中的选项,该选项从0.01
to取值1
。
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 逻辑,例如,操作状态 let's day。
以下是如何做到这一点:
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 中添加元素之后,我们需要将 future 控制权传递给 jQuery。否则,ReactJS 永远不会重新渲染我们的组件,但我们不希望那样。我们希望 jQuery 负责所有更新。
React 生命周期方法来拯救你了!
使用 shouldComponentUpdate() 让 React 知道组件的输出是否不受当前 state 或 props 变化的影响。默认行为是在每次状态更改时重新渲染,并且在绝大多数情况下,但我们不希望这种行为!
shouldComponentUpdate()
在接收新props或状态时在渲染之前调用。如果shouldComponentUpdate()
返回false
,则componentWillUpdate()
、render()
和componentDidUpdate()
将不会被调用。
然后,我们使用componentWillReceiveProps()
,我们比较this.props
有nextProps
和调用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
};
第五步:清理乱七八糟的东西。
许多 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 组件更像是 3rd 方库的包装器。主要是通过使用 componentDidMount/componentWillUnmount 来初始化/销毁第三方库。和props作为一种方式,让父级自定义子级包装的第三方库的行为并连接插件事件。
您可以使用这种方法集成几乎所有 jQuery 插件!
React 不能很好地与执行直接 DOM 突变的库配合使用。如果其他东西改变了 React 试图渲染的 DOM,它会抛出错误。如果你必须完成这项工作,你最好的折衷是让页面的不同部分由不同的东西管理,例如一个包含你的 jquery 组件的 div,然后是一些包含你的 React 组件的其他 div( s)。在这些不同的(jquery 和 react)组件之间进行通信会很困难,但老实说,最好选择其中一个。
虽然在技术上无懈可击,但 Kayolan 的回答有一个致命的缺陷,恕我直言:在将未来 UI 更新的责任从 React 转移到 jQuery 时,他一开始就否定了 React 存在的意义!React 控制可排序列表的初始呈现,但此后,只要用户执行第一个 jQueryUI 拖动/排序操作,React 的状态数据就会过时。React 的全部意义在于在视图级别表示您的状态数据。
所以,当我解决这个问题时,我采取了相反的方法:我试图确保 React 尽可能地处于控制之中。我不让jQueryUI的可排序控制改变DOM可言。
这怎么可能?好吧,jQuery-ui 的 sortable() 方法有一个cancel
调用,可以将 UI 设置回开始拖放内容之前的状态。诀窍是在发出该cancel
调用之前读取可排序控件的状态。这样,在调用将 DOM 设置回原来的方式之前,我们可以了解用户的意图cancel
。一旦我们有了这些意图,我们就可以将它们传递回 React,并按照用户想要的新顺序操作状态数据。最后,对该数据调用 setState() 以让 React 呈现新订单。
这是我如何做到的:
- 将 jquery-ui.sortable() 方法附加到行项目列表(当然是由 React 生成的!)
- 让用户在 DOM 周围拖放这些行项目。
- 当用户开始拖动时,我们读取用户从中拖动的行项目的索引。
- 当用户删除订单项时,我们:
- 从 jQuery-ui.sortable() 读取行项目的新索引位置,即用户在列表中删除它的位置。
- 将
cancel
调用传递给 jQuery-ui.sortable() 以便列表返回到其原始位置,并且 DOM 不变。 - 将拖动的行项目的旧索引和新索引作为参数传递给 React module中的 JavaScript 函数。
- 让该函数将列表的后端状态数据重新排序为用户将其拖放到的新顺序。
- 进行 React
setState()
调用。
UI 中的列表现在将反映我们状态数据的新顺序;这是标准的 React 功能。
因此,我们可以使用 jQueryUI Sortable 的拖放功能,但它根本不会改变 DOM。React 很高兴,因为它控制着 DOM(它应该在哪里)。
https://github.com/brownieboy/react-dragdrop-test-simple 上的Github 存储库示例。这包括指向现场演示的链接。
我无法让 jquery-ui npm 包工作。对我有用的是使用 jquery-ui-bundle:
import $ from 'jquery';
import 'jquery-ui-bundle';
import 'jquery-ui-bundle/jquery-ui.min.css';
关于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>
);
}
}
对于 id,我使用UUID的,所以在匹配它们时我没有冲突orderSortableListsInState()
。