Creación del complemento Vuex Undo / Redo para VueJS

imagen


Hay muchas ventajas en centralizar el estado de su aplicación en la tienda Vuex. Una ventaja es que todas las transacciones se registran. Esto le permite utilizar funciones convenientes, como la depuración en tiempo de ejecución , donde puede cambiar entre estados anteriores para separar las tareas de la ejecución.


En este artículo, mostraré cómo crear la función Deshacer / Rehacer más Rollback / Return usando Vuex, que funciona de manera similar a la depuración durante la depuración. Esta característica se puede usar en una variedad de escenarios, desde formas complejas hasta juegos basados ​​en navegador.


Puede consultar el código terminado aquí en Github y probar la demostración en este Codepen . También creé un complemento como un módulo NPM llamado vuex-undo-redo si quieres usarlo en un proyecto.


Nota: Este artículo se publicó originalmente aquí en el blog para desarrolladores Vue.js 2017/11/13.

Configuración del complemento


Para que esta característica sea reutilizable, la crearemos como un complemento Vue. Esta función requiere que agreguemos algunos métodos y datos a la instancia de Vue, por lo que estructuramos el complemento como un mixin.


module.exports = { install(Vue) { Vue.mixin({ // Code goes here }); } }; 

Para usarlo en un proyecto, simplemente podemos importar el complemento y conectarlo:


 import VuexUndoRedo from './plugin.js'; Vue.use(VuexUndoRedo); 

Idea


La función funcionará al revertir la última mutación si el usuario desea cancelarla y luego volver a aplicarla si quiere repetirla. ¿Cómo hacemos esto?


Enfoque n. ° 1


El primer enfoque posible es tomar "instantáneas" del estado del repositorio después de cada mutación y colocar la instantánea en una matriz. Para deshacer / rehacer, podemos obtener la instantánea correcta y reemplazarla con el estado de almacenamiento.


El problema con este enfoque es que el estado del repositorio es un objeto JavaScript. Cuando coloca un objeto JavaScript en una matriz, simplemente coloca una referencia al objeto. Una implementación ingenua, como la siguiente, no funcionará:


 var state = { ... }; var snapshot = []; // Push the first state snapshot.push(state); // Push the second state state.val = "new val"; snapshot.push(state); // Both snapshots are simply a reference to state console.log(snapshot[0] === snapshot[1]); // true 

El enfoque de la instantánea requerirá que primero haga un clon de estado antes de empujar. Dado que el estado de Vue se vuelve reactivo al agregar automáticamente las funciones get y set, no funciona muy bien con la clonación.


Enfoque n. ° 2


Otro enfoque posible es registrar cada mutación fija. Para cancelar, restablecemos la tienda a su estado inicial y luego ejecutamos las mutaciones nuevamente; todos menos el último. Reembolso concepto similar.


Dados los principios de Flux, reiniciar mutaciones desde el mismo estado inicial idealmente debería recrear el estado. Dado que este es un enfoque más limpio que el primero, continuemos.


Registro de mutaciones.


Vuex ofrece un método API para suscribirse a mutaciones, que podemos usar para registrarlas. Estableceremos esto en el gancho created . En la devolución de llamada, simplemente colocamos la mutación en una matriz que luego se puede volver a ejecutar.


 Vue.mixin({ data() { return { done: [] } }, created() { this.$store.subscribe(mutation => { this.done.push(mutation); } } }); 

Método de reversión


Para cancelar la mutación, borramos el repositorio y luego volvemos a ejecutar todas las mutaciones, excepto la última. Así es como funciona el código:


  1. Use el método de matriz pop para eliminar la última mutación.
  2. Borrar el estado de la tienda usando la mutación especial EMPTY_STATE (explicada a continuación)
  3. Repita cada mutación restante, reparándola nuevamente en la nueva tienda. Tenga en cuenta que el método de suscripción todavía está activo durante este proceso, es decir, cada mutación se agregará nuevamente. Eliminarlo de inmediato con pop .

 const EMPTY_STATE = 'emptyState'; Vue.mixin({ data() { ... }, created() { ... }, methods() { undo() { this.done.pop(); this.$store.commit(EMPTY_STATE); this.done.forEach(mutation => { this.$store.commit(`${mutation.type}`, mutation.payload); this.done.pop(); }); } } }); 

Tienda de limpieza


Cada vez que se usa este complemento, el desarrollador debe implementar una mutación en su repositorio llamada emptyState. El desafío es devolver la tienda a su estado original para que esté lista para recuperarse desde cero.


El desarrollador debe hacer esto por su cuenta, porque el complemento que creamos no tiene acceso a la tienda, solo a la instancia de Vue. Aquí hay un ejemplo de implementación:


 new Vuex.Store({ state: { myVal: null }, mutations: { emptyState() { this.replaceState({ myval: null }); } } }); 

Volviendo a nuestro plugin, la mutación emptyState no debe agregarse a nuestra lista de resultados, ya que no queremos volver a confirmar esto durante el proceso de reversión. Prevenga esto con la siguiente lógica:


 Vue.mixin({ data() { ... }, created() { this.$store.subscribe(mutation => { if (mutation.type !== EMPTY_STATE) { this.done.push(mutation); } }); }, methods() { ... } }); 

Método de devolución


Creemos una nueva propiedad de datos, undone , que será una matriz. Cuando eliminamos la última mutación de done durante el proceso de reversión, la colocamos en esta matriz:


 Vue.mixin({ data() { return { done: [], undone: [] } }, methods: { undo() { this.undone.push(this.done.pop()); ... } } }); 

Ahora podemos crear un método de redo que simplemente tomará la última mutación undone agregada y la volverá a undone .


 methods: { undo() { ... }, redo() { let commit = this.undone.pop(); this.$store.commit(`${commit.type}`, commit.payload); } } 

No hay devolución posible


Si el usuario inicia la cancelación una o más veces y luego realiza una nueva confirmación nueva, el contenido de undone se invalidará. Si esto sucede, debemos vaciar el undone .


Podemos descubrir nuevas confirmaciones de nuestra devolución de llamada de suscripción al agregar una confirmación. Sin embargo, la lógica es complicada, ya que la devolución de llamada no tiene una forma obvia de averiguar qué es una nueva confirmación y qué es deshacer / rehacer.


El enfoque más fácil es establecer la nueva bandera de Mutación. Será verdadero por defecto, pero los métodos de reversión y retorno lo establecerán temporalmente en falso. Si la mutación se establece en verdadera, la devolución de llamada de subscribe borrará la matriz undone .


 module.exports = { install(Vue) { Vue.mixin({ data() { return { done: [], undone: [], newMutation: true }; }, created() { this.$store.subscribe(mutation => { if (mutation.type !== EMPTY_STATE) { this.done.push(mutation); } if (this.newMutation) { this.undone = []; } }); }, methods: { redo() { let commit = this.undone.pop(); this.newMutation = false; this.$store.commit(`${commit.type}`, commit.payload); this.newMutation = true; }, undo() { this.undone.push(this.done.pop()); this.newMutation = false; this.$store.commit(EMPTY_STATE); this.done.forEach(mutation => { this.$store.commit(`${mutation.type}`, mutation.payload); this.done.pop(); }); this.newMutation = true; } } }); }, } 

¡La funcionalidad principal ahora está completa! Agregue el complemento a su propio proyecto o a mi demo para probarlo.


API pública


En mi demostración, notará que los botones cancelar y regresar están deshabilitados si su funcionalidad no es posible actualmente. Por ejemplo, si todavía no hubo confirmaciones, obviamente no puede cancelar o rehacer. Un desarrollador que use este complemento puede querer implementar una funcionalidad similar.


Para habilitar esto, el complemento puede proporcionar dos propiedades calculadas, canUndo y canRedo como parte de la API pública. Esto es trivial de implementar:


 module.exports = { install(Vue) { Vue.mixin({ data() { ... }, created() { ... }, methods: { ... }, computed: {}, computed: { canRedo() { return this.undone.length; }, canUndo() { return this.done.length; } }, }); }, } 

Source: https://habr.com/ru/post/es433986/


All Articles