Ответ 1
Если вам действительно нужно это сделать, вот подход, который я использую.
План: Создать компонент для управления плагином jQuery. Этот компонент предоставит ориентированное на React представление компонента jQuery. Более того, он будет:
- Используйте методы жизненного цикла React для инициализации и срыва плагина jQuery;
- Используйте React
props
в качестве параметров конфигурации плагина и подключайтесь к событиям методов плагинов; - Уничтожьте плагин при отключении компонентов.
Давайте рассмотрим практический пример, как это сделать с плагином jQuery UI Sortable.
TL;DR: окончательная версия
Если вы просто хотите получить окончательную версию обернутого примера экземпляра 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');
}
};
По желанию вы можете установить реквизиты по умолчанию (в случае отсутствия переданы) и типы поддержки:
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: Передайте параметры конфигурации через реквизиты
Скажем, мы хотим настроить непрозрачность помощника при сортировке. Мы будем использовать параметр opacity
в конфигурации плагина, который принимает значения от 0.01
до 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 Sortable.
Шаг 3: Подключения к событиям плагина.
Вам, скорее всего, придется подключиться к некоторым из методов плагина, чтобы выполнить некоторую логику React, например, манипулировать состоянием let 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, нам нужно передать будущий элемент управления jQuery. В противном случае ReactJS никогда не будет повторно отображать наш компонент, но мы этого не хотим. Мы хотим, чтобы jQuery отвечал за все обновления.
Способы жизненного цикла реагирования приходят на помощь!
Использовать shouldComponentUpdate(), чтобы позволить React знать, не влияет ли на компонентный вывод текущее изменение состояния или реквизита. Поведение по умолчанию заключается в повторном рендеринге при каждом изменении состояния и в подавляющем большинстве, но мы не хотим этого поведения!
shouldComponentUpdate()
вызывается перед рендерингом при получении новых реквизитов или состояний. Если shouldComponentUpdate()
возвращает false
, то componentWillUpdate()
, render()
и componentDidUpdate()
не будут вызываться.
Затем мы используем componentWillReceiveProps()
, мы сравниваем this.props
с nextProps
и вызываем jQuery UI сортируемые обновления только при необходимости. В этом примере мы реализуем параметр enable/disable 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 и уничтожить. Реагирующие методы жизненного цикла снова оказываются на спасителе и обеспечивают механизм захвата, когда компонент размонтируется.
class Sortable extends React.Component {
// ... omitted for brevity
componentWillUnmount() {
// Clean up the mess when the component unmounts
this.$node.sortable('destroy');
}
// ... omitted for brevity
};
Заключение
Плагины плагинов jQuery с React не всегда являются лучшим выбором. Тем не менее, приятно знать, что это вариант и как вы можете реализовать решение. Это жизнеспособный вариант, если вы переносите устаревшее приложение jQuery на React или, возможно, просто не можете найти плагин React, который соответствует вашим потребностям в вашем случае.
В случае, если библиотека изменяет DOM, мы пытаемся сохранить React на своем пути. Реакция работает лучше всего, когда она полностью контролирует DOM. В этих случаях компоненты React являются более обертками для сторонних библиотек. В основном, используя компонентDidMount/componentWillUnmount для инициализации/уничтожения сторонней библиотеки. И реквизиты как способ дать родителям способ настройки поведения сторонней библиотеки, которую ребенок обертывает и подключать к событиям плагина.
Вы можете использовать этот подход для интеграции практически любого плагина jQuery!