Cómo usar jQuery UI con React JS
jquery ui npm (5)
¿Cómo puedo usar jQuery UI con React? He visto un par de ejemplos en Google, pero todos parecen estar desactualizados.
Aunque técnicamente es impecable, la respuesta de Kayolan tiene un defecto fatal, en mi humilde opinión: al pasar la responsabilidad de las futuras actualizaciones de la interfaz de usuario de React a jQuery, ¡él negó el punto de que React estuviera allí en primer lugar! React controla el procesamiento inicial de la lista ordenable, pero después de eso, los datos de estado de React quedarán desactualizados tan pronto como el usuario realice las primeras operaciones de arrastre / ordenación de jQueryUI. Y el objetivo de React es representar sus datos de estado en el nivel de vista.
Entonces, tomé el enfoque inverso cuando abordé este problema: intenté asegurarme de que React tuviera el control lo más posible. No dejo que el control jQueryUI Sortable cambie el DOM en absoluto .
¿Como es eso posible?
Bueno, el método sortable () de jQuery-ui tiene una llamada de
cancel
que restablece la interfaz de usuario a como estaba antes de comenzar a arrastrar y soltar cosas.
El truco es leer el estado del control ordenable
antes de
emitir esa llamada de
cancel
.
De esa manera, podemos retomar cuáles eran las
intenciones
del usuario, antes de que la llamada de
cancel
vuelva a poner el DOM como estaba.
Una vez que tengamos esas intenciones, podemos pasarlas de nuevo a React y manipular los datos de estado para que estén en el nuevo orden que el usuario quería.
Finalmente, llame a un setState () en esos datos para que React represente el nuevo orden.
Así es como hago eso:
- Adjunte el método jquery-ui.sortable () a una lista de líneas de pedido (¡generada por React, por supuesto!)
- Deje que el usuario arrastre y suelte esas líneas de pedido alrededor del DOM.
- Cuando el usuario comienza a arrastrar, leemos el índice de la línea de pedido desde la que arrastra el usuario.
-
Cuando el usuario suelta la línea de pedido, nosotros:
- Lea desde jQuery-ui.sortable () la nueva posición de índice para la línea de pedido, es decir, en qué parte de la lista el usuario la dejó .
-
Pase una llamada de
cancel
a jQuery-ui.sortable () para que la lista regrese a su posición original y el DOM no cambie. - Pase los índices antiguos y nuevos de la línea de pedido arrastrada como parámetros a una función de JavaScript en un módulo Reaccionar.
- Haga que esa función reordene los datos de estado de fondo de la lista para que estén en el nuevo orden en que el usuario los arrastró y soltó.
-
Realice una
setState()
ReactsetState()
.
La lista en la interfaz de usuario ahora reflejará el nuevo orden de los datos de nuestro estado; Esta es la funcionalidad estándar de React.
Entonces, podemos usar la funcionalidad de arrastrar y soltar de jQueryUI Sortable, pero sin cambiar el DOM en absoluto. React está contento porque tiene el control del DOM (donde debería estar).
Ejemplo de repositorio de Github en https://github.com/brownieboy/react-dragdrop-test-simple . Esto incluye un enlace a una demostración en vivo.
En cuanto a la larga respuesta de Kaloyan Kosev , ¿debo crear un componente para cada característica jQueryUi que quiero usar? ¡No, gracias! ¿Por qué no simplemente actualizar su estado cuando cambia el DOM ? Followig funciona para mí:
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.
}
El elemento de lista solo necesita un atributo adicional para permitir que jQuery seleccione el elemento.
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>
);
}
}
Para los ID utilizo los
UUID
, por lo que no
orderSortableListsInState()
conflictos al hacerlos coincidir en
orderSortableListsInState()
.
No pude hacer funcionar el paquete jquery-ui npm. Lo que me ha funcionado es usar jquery-ui-bundle:
import $ from ''jquery'';
import ''jquery-ui-bundle'';
import ''jquery-ui-bundle/jquery-ui.min.css'';
React no juega bien con bibliotecas que hacen mutaciones DOM directas. Si algo más muta el DOM donde React está intentando renderizar, arrojará errores. Si tuviera que hacer que esto funcione, su mejor compromiso es tener diferentes partes de su página que son administradas por diferentes cosas, por ejemplo, un div que alberga sus componentes jquery, y luego otro div que contiene su componente React ( s) Sin embargo, la comunicación entre estos componentes dispares (jquery y react) será difícil y, sinceramente, probablemente sea mejor elegir uno u otro.
Si realmente necesita hacer eso, aquí hay un enfoque que estoy usando.
El plan: crear un componente para administrar el complemento jQuery . Este componente proporcionará una vista centrada en React del componente jQuery. Además:
- Utilice los métodos del ciclo de vida React para inicializar y eliminar el complemento jQuery;
-
Utilice los
props
React como opciones de configuración del complemento y conéctese a los eventos de métodos del complemento; - Destruya el complemento cuando el componente se desmonte.
Exploremos un ejemplo práctico de cómo hacerlo con el complemento jQuery UI Sortable .
TLDR: la versión final
Si solo desea obtener la versión final del ejemplo ordenado de jQuery UI envuelto:
- aquí hay un GIST que hice con comentarios completos anotados;
- y aquí hay una DEMO jsfiddle , comentarios completos anotados también;
... además, a continuación se muestra el fragmento del código de comentarios más largo :
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'');
}
};
Opcionalmente, puede establecer accesorios predeterminados (en el caso de que no se pase ninguno) y los tipos de accesorios:
Sortable.defaultProps = {
opacity: 1,
enable: true
};
Sortable.propTypes = {
opacity: React.PropTypes.number,
enable: React.PropTypes.bool,
onChange: React.PropTypes.func.isRequired
};
... y
<Sortable />
aquí cómo usar el
<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''));
La explicación completa
Para aquellos de ustedes que quieren entender por qué y cómo . Aquí hay una guía paso a paso:
Paso 1: crea un componente.
Nuestro componente aceptará una matriz (lista) de elementos (cadenas) como accesorio de
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>
);
}
};
Paso 2: Pase las opciones de configuración a través de accesorios
Digamos que queremos configurar
la opacidad del ayudante mientras ordenamos
.
Utilizaremos la opción de
opacity
en la configuración del complemento, que toma valores de
0.01
a
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
};
Y así es como podemos usar el componente en nuestro código ahora:
<Sortable opacity={0.8} />
De la misma manera, podemos mapear cualquiera de las opciones de jQUery UI Sortable .
Paso 3: Funciones de conexión en eventos de complementos.
Lo más probable es que necesites conectarte a algunos de los métodos de complementos para realizar alguna lógica de React, por ejemplo, manipular el estado.
Aquí se explica cómo hacerlo:
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
};
Y aquí está cómo usarlo:
<Sortable
opacity={0.8}
onChange={ (event, ui) => console.log(''DOM changed!'', event, ui) } />
Paso 4: Pase el control de actualizaciones futuras a jQuery
Justo después de que ReactJS agrega el elemento en el DOM real, necesitamos pasar el control futuro a jQuery. De lo contrario, ReactJS nunca volverá a representar nuestro componente, pero no queremos eso. Queremos que jQuery sea responsable de todas las actualizaciones.
Reaccionar los métodos del ciclo de vida viene al rescate!
Use shouldComponentUpdate () para informar a React si la salida de un componente no se ve afectada por el cambio actual de estado o accesorios. El comportamiento predeterminado es volver a renderizar en cada cambio de estado, y en la gran mayoría, ¡pero no queremos este comportamiento!
shouldComponentUpdate()
se invoca antes de renderizar cuando se reciben nuevos accesorios o estados.
Si
shouldComponentUpdate()
devuelve
false
, no se invocará
componentWillUpdate()
,
render()
y
componentDidUpdate()
.
Luego, utilizamos
componentWillReceiveProps()
, comparamos
this.props
con
nextProps
y llamamos a las actualizaciones ordenables de jQuery UI solo cuando es necesario.
Para este ejemplo, implementaremos la opción habilitar / deshabilitar de 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
};
Paso 5: limpia el desorden.
Muchos complementos de jQuery proporcionan un mecanismo para limpiar después de ellos mismos cuando ya no son necesarios. jQuery UI Sortable proporciona un evento que podemos activar para decirle al complemento que desvincula sus eventos DOM y los destruya. Los métodos de reacción del ciclo de vida vuelven al rescate nuevamente y proporcionan un mecanismo para engancharse cuando se desmonta el componente.
class Sortable extends React.Component {
// ... omitted for brevity
componentWillUnmount() {
// Clean up the mess when the component unmounts
this.$node.sortable(''destroy'');
}
// ... omitted for brevity
};
Conclusión
Envolver los complementos de jQuery con React no siempre es la mejor opción. Sin embargo, es bueno saber que es una opción y cómo puede implementar una solución. Es una opción viable si está migrando una aplicación jQuery heredada a React o tal vez simplemente no puede encontrar un complemento React que se adapte a sus necesidades en su caso.
En el caso de que una biblioteca modifique el DOM, tratamos de mantener React fuera de su camino . Reaccionar funciona mejor cuando tiene control total del DOM. En estos casos, los componentes React son más envoltorios para las bibliotecas de terceros. Principalmente mediante el uso de componentDidMount / componentWillUnmount para inicializar / destruir la biblioteca de terceros. Y los accesorios como una forma de dar al padre una forma de personalizar el comportamiento de la biblioteca de terceros que el niño envuelve y conectar en eventos de complementos.
¡Puede usar este enfoque para integrar casi cualquier complemento jQuery !