Creación de soluciones de gestión de estado para editores de herramientas creativas

Nov 28 2022
Introducción En este artículo, compartiremos algunas de nuestras ideas (ligeramente obstinadas) sobre el diseño y la creación de soluciones de administración de estado escalables en el contexto de los editores de herramientas creativas con React, Immer y Recoil como fundamentos de nuestro enfoque. Para ilustrar mejor nuestro enfoque, incluiremos algunos ejemplos de código y un pequeño prototipo.

Introducción

En este artículo, compartiremos algunos de nuestros puntos de vista (ligeramente obstinados) sobre el diseño y la creación de soluciones de gestión de estado escalables en el contexto de los editores de herramientas creativas con React, Immer y Recoil como fundamentos de nuestro enfoque.

Para ilustrar mejor nuestro enfoque, incluiremos algunos ejemplos de código y un pequeño prototipo.

Hemos estado utilizando este conocimiento en un par de proyectos diferentes, lo que nos permite escapar fácilmente de algunas de las trampas más comunes.

Estado de la aplicación frente al estado del diseño

Una de las principales distinciones que podemos hacer en este tipo de aplicaciones es el estado de la aplicación frente al estado del diseño.

Estado de diseño

Describe el contenido creado por el usuario, por ejemplo: texto (fuente, tamaño, color, contenido), imágenes, elementos agrupados, elementos conectados, etc.

El estado del diseño cambia cada vez que el diseño cambia conceptualmente, pero no cuando el usuario realiza acciones que no afectan el diseño.

Algunos ejemplos de acciones que impactan en el estado de diseño:

  • Mover un elemento
  • Cambiar las propiedades de un texto (color, tamaño, fuente)
  • Agrupar elementos

Define el estado actual de la aplicación, excluyendo el diseño. El estado de la aplicación cambia cuando el usuario interactúa con la aplicación y sus menús.

Algunos ejemplos:

  • Seleccione un elemento
  • Abrir un cuadro de diálogo para editar las propiedades de un elemento
  • Mostrar el menú contextual
  • Confirmar una acción

Cada tipo de estado tiene sus propias particularidades, que no se aplican al otro, y harían que nuestra implementación fuera más compleja si se mantuvieran juntos.

Por ejemplo:

  • Al guardar un diseño, solo se guarda esta parte del estado.
  • Deshacer/rehacer solo se aplica al estado de diseño. La aplicación debe poder funcionar con pequeñas inconsistencias entre el diseño y el estado de la aplicación, por ejemplo, cuando el elemento seleccionado ya no está en el diseño debido a un deshacer.
  • Al cargar un diseño, el estado del diseño simplemente se establece con el diseño, mientras que el estado de la aplicación se inicializa principalmente con un valor predeterminado.

estado de la aplicación

Siempre que podamos, debemos favorecer el estado local sobre el estado global, el estado de la aplicación no es una excepción.

El análisis del código y su flujo se vuelve más fácil cuando usamos el estado local, los componentes deben pasar datos de forma explícita entre sí y sus interacciones son explícitas, no hay efectos de uso "lejanamente ocultos" que tengamos que buscar manualmente.

Estado de diseño

A la hora de definir el estado de diseño, debemos tener las siguientes consideraciones:

  • Ser capaz de obtener y configurar todo el estado para que podamos implementar fácilmente:
    – Cargar y guardar un diseño
    – Deshacer/rehacer
  • Evite renderizar todos los elementos cuando solo cambien algunos de ellos

Una de las formas más fáciles de implementar deshacer/rehacer en una aplicación React es usar I mmer para generar parches de modificación.

Immer es una biblioteca JS que nos permite escribir modificaciones de datos en un contexto inmutable (por ejemplo, estado React) de una "manera mutable".

Otra característica de Immer es la generación de parches para hacer y deshacer de una modificación, esto nos permite guardar cómo volver a ejecutar el cambio que hicimos y cómo volver al estado de vistas previas.

Después de cada cambio de estado, debemos guardar los parches de hacer y deshacer, y usarlos cuando el usuario active el deshacer/rehacer.

¿Por qué necesitamos obtener y establecer todo el estado al mismo tiempo?

La alternativa es no tenerlo todo junto, esto no parece ser un gran problema para cargar y guardar, solo tenemos que obtener/establecer todas las diferentes partes del estado.

Pero se convierte en un problema cuando tenemos que implementar deshacer/rehacer. Para cada parte de estado, tenemos que guardar los parches producidos, con metadatos que indiquen para qué parte de estado es, y leerlos cuando se activa un deshacer para que podamos modificar la parte de estado correcta.

Además, debido a que una acción de usuario puede modificar varias partes de estado como una sola operación, debemos realizar un seguimiento de qué parches pertenecen a la misma operación para poder deshacerlos y rehacerlos al mismo tiempo.

Usar un solo estado resolvería todas estas cosas

  • No hay acciones que modifiquen múltiples estados.
  • Todos los parches se aplican al estado, no a varios estados distribuidos.

La forma más fácil de satisfacer esta elección de diseño es guardar todo el estado del diseño en el mismo lugar. Eso nos haría pensar que useState<DesignState>(defaultState), como probablemente suponga, esto hace que fallemos en nuestra consideración de "renderizar la mayor parte de la aplicación":

No mostrar la mayor parte de la aplicación cuando cambia el diseño

Para resolver esto, generalmente usamos Recoil, una biblioteca de administración de estado para React.

Recoil tiene dos conceptos principales : átomos y selectores.

Átomos : unidad de estado, que se puede usar de manera similar a useState, pero globalmente, por ejemplo

Como useStateimplementación, en el código anterior, todos los DesignElements se representarán cada vez que cambie cualquier elemento (o cualquier parte del estado). Para solucionar esto, podemos usar selectores.

Selectores : funciones que crean una proyección a partir de un átomo u otro selector. Cuando un componente de React usa un selector, se volverá a procesar solo (con algunas advertencias) cuando cambie el resultado del selector.

Recoil también nos permite recibir argumentos en la función que define el selector, ese tipo de selectores se llaman selectorFamily. Podemos usar esto para crear un selectorFamily que recibe un elementId y nos da el elemento.

Cuando se modifica un elemento, el código anterior solo activa una actualización del DesignElement coincidente y no representa todos los elementos ni el componente de diseño.

Un observador atento puede ver que el componente de diseño se renderizará cada vez que se agregue o elimine un componente, lo que activará la nueva renderización de todos los DesignElements. Si esto causa problemas de rendimiento para un caso de uso particular, podemos envolver nuestro componente DesignElement en un archivo React.memo.

establecer el estado

Debido a que queremos que nuestros conjuntos se apliquen al DesignState de nivel superior (para simplificar deshacer/rehacer), podemos crear devoluciones de llamada de retroceso para encapsular nuestra lógica de modificación y la creación de parches de deshacer/rehacer.

Un ejemplo mínimo se puede encontrar en este Codesanbox.

Clausura

En esta publicación de blog, compartimos cuáles son los principales impulsores al diseñar la gestión de estado en el contexto de los editores de herramientas creativas y nuestra implementación de referencia que los satisface.

Si lo leíste hasta aquí me encantaría saber tu opinión. Puedes contactarme aquí .

En Zeppelin Labs, ayudamos a los fundadores y empresas en crecimiento a experimentar y crear productos digitales diferenciados que impulsan el crecimiento. Puede encontrarnos aquí . O aquí _ O aquí _

Si desea unirse a nuestro equipo, envíenos un correo electrónico a [email protected]

¿Te gustaría asociarte? envíenos un correo electrónico a [email protected]

Suscríbete a nuestro Newsletter aquí .