Luchando por el desempeño de formas React verdaderamente grandes

En uno de los proyectos, encontramos formularios de varias docenas de bloques que dependen unos de otros. Como de costumbre, no podemos hablar de la tarea en detalle debido a la NDA, pero trataremos de describir nuestra experiencia de "domesticar" el desempeño de estas formas usando un ejemplo abstracto (aunque sea ligeramente no vital). Te diré qué conclusiones hemos sacado de un proyecto de React con Final-form.



imagen


Imagine que el formulario le permite obtener un pasaporte extranjero de una nueva muestra, mientras procesa la recepción de una visa Schengen a través de un intermediario: un centro de visas. Este ejemplo parece lo suficientemente burocrático como para demostrar nuestras complejidades.



Entonces, en nuestro proyecto, nos enfrentamos a una forma de muchos bloques con ciertas propiedades:



  • Entre los campos hay cuadros de entrada, selección múltiple, campos de autocompletar.
  • Los bloques están vinculados. Suponga que en un bloque necesita especificar los datos del pasaporte interno, y justo debajo habrá un bloque con los datos del solicitante de la visa. Al mismo tiempo, también se emite un acuerdo con un centro de visas para un pasaporte interno.

  • – , , ( 10 , ) .
  • , , . , 10- , . : .
  • . . .


La forma final ocupó alrededor de 6 mil píxeles verticalmente, esto es alrededor de 3-4 pantallas, en total, más de 80 campos diferentes. En comparación con este formulario, las aplicaciones en los Servicios del Estado no parecen tan buenas. Lo más parecido en cuanto a la abundancia de preguntas es probablemente un cuestionario del servicio de seguridad a alguna gran corporación o una aburrida encuesta de opinión sobre las preferencias de contenido de vídeo.



Las formas grandes no son tan comunes en problemas reales. Si intentamos implementar este formulario "de frente", por analogía con la forma en que estamos acostumbrados a trabajar con formularios pequeños, el resultado será imposible de usar.



El principal problema es que al ingresar cada letra en los campos correspondientes, se volverá a dibujar todo el formulario, lo que conlleva problemas de rendimiento, especialmente en dispositivos móviles.



Y es difícil hacer frente al formulario no solo para los usuarios finales, sino también para los desarrolladores que deben mantenerlo. Si no toma medidas especiales, es difícil rastrear la relación de los campos en el código; los cambios en un lugar conllevan consecuencias que a veces son difíciles de predecir.



Cómo implementamos Final-form



El proyecto usó React y TypeScript (cuando completamos nuestras tareas, cambiamos completamente a TypeScript). Por lo tanto, para implementar los formularios, tomamos la biblioteca React Final-form de los creadores de Redux Form.



Al comienzo del proyecto, dividimos el formulario en bloques separados y usamos los enfoques descritos en la documentación para Final-form. Por desgracia, esto llevó al hecho de que la entrada en uno de los campos arrojó un cambio en todo el formulario grande. Dado que la biblioteca es relativamente reciente, la documentación aún es reciente. No describe las mejores recetas para mejorar el rendimiento de moldes grandes. Según tengo entendido, muy pocas personas se enfrentan a esto en proyectos. Y para formularios pequeños, algunos redibujos innecesarios del componente no tienen ningún efecto sobre el rendimiento.



Dependencias



La primera oscuridad que tuvimos que enfrentar fue cómo implementar exactamente la dependencia entre los campos. Si trabaja estrictamente de acuerdo con la documentación, el formulario demasiado grande comienza a ralentizarse debido a la gran cantidad de campos interconectados. El punto son las dependencias. La documentación sugiere poner una suscripción a un campo externo al lado del campo. Así fue en nuestro proyecto: versiones adaptadas de react-final-form-listeners, que se encargaban de vincular los campos, estaban en el mismo lugar que los componentes, es decir, estaban en todos los rincones. Las dependencias eran difíciles de rastrear. Esto infló la cantidad de código: los componentes eran gigantes. Y todo funcionó lentamente. Y para cambiar algo en el formulario, uno tenía que pasar mucho tiempo usando la búsqueda en todos los archivos del proyecto (hay alrededor de 600 archivos en el proyecto, más de 100 de ellos son componentes).



Hicimos varios intentos para mejorar la situación.



Tuvimos que implementar nuestro propio selector, que selecciona solo los datos que necesita un bloque en particular.



<Form onSubmit={this.handleSubmit} initialValues={initialValues}>
   {({values, error, ...other}) => (
      <>
      <Block1 data={selectDataForBlock1(values)}/>
      <Block2 data={selectDataForBlock2(values)}/>
      ...
      <BlockN data={selectDataForBlockN(values)}/>
      </>
   )}
</Form>


Como puedes imaginar, tuve que inventar el mío memoize pick([field1, field2,...fieldn]).



Todo esto en conjunto con PureComponent (React.memo, reselect)llevó a que los bloques se redibujen solo cuando los datos de los que dependen cambian (sí, introdujimos en el proyecto la biblioteca Reselect, que no se usaba anteriormente, con su ayuda realizamos casi todas las solicitudes de datos).



Como resultado, cambiamos a un oyente, que describe todas las dependencias del formulario. Tomamos la idea misma de este enfoque del proyecto final-form-calculate ( https://github.com/final-form/final-form-calculate ) y lo agregamos a nuestras necesidades.



<Form
   onSubmit={this.handleSubmit}
   initialValues={initialValues}
   decorators={[withContextListenerDecorator]}
>

   export const listenerDecorator = (context: IContext) =>
   createDecorator(
      ...block1FieldListeners(context),
      ...block2FieldListeners(context),
      ...
   );

   export const block1FieldListeners = (context: any): IListener[] => [
      {
      field: 'block1Field',
      updates: (value: string, name: string) => {
         //    block1Field       ...
         return {
            block2Field1: block2Field1NewValue,
            block2Field2: block2Field2NewValue,
         };
      },
   },
];


Como resultado, obtuvimos la dependencia requerida entre los campos. Además, los datos se almacenan en un solo lugar y se utilizan de forma más transparente. Además, sabemos en qué orden se activan las suscripciones, ya que esto también es importante.



Validación



Por analogía con las dependencias, nos hemos ocupado de la validación.



En casi todos los campos, necesitábamos verificar si la persona ingresó la edad correcta (por ejemplo, si el conjunto de documentos corresponde a la edad especificada). De docenas de validadores diferentes distribuidos en todos los formularios, cambiamos a uno global, dividiéndolo en bloques separados:



  • validador de datos de pasaporte,
  • validador de datos de viaje,
  • para obtener datos sobre visas emitidas anteriormente,
  • etc.


Esto casi no afectó el rendimiento, pero aceleró un mayor desarrollo. Ahora, al realizar cambios, no es necesario que revise todo el archivo para comprender lo que sucede en los validadores individuales.



Reutilización de código



Comenzamos con un formulario grande, en el que ejecutamos nuestras ideas, pero con el tiempo el proyecto creció: apareció otro formulario. Naturalmente, en el segundo formulario, usamos todas las mismas ideas e incluso reutilizamos el código.



Anteriormente, ya hemos movido toda la lógica a módulos separados, así que ¿por qué no conectarlos al nuevo formulario? De esta manera hemos reducido significativamente la cantidad de código y la velocidad de desarrollo.



De manera similar, el nuevo formulario ahora tiene tipos, constantes y componentes comunes con el anterior; por ejemplo, tienen autorización general.



En lugar de totales



La pregunta es lógica: ¿por qué no usamos otra biblioteca para formularios, ya que esta tenía dificultades? Pero las formas grandes tendrán sus propios problemas de todos modos. En el pasado, yo mismo he trabajado con Formik. Teniendo en cuenta que encontramos soluciones a nuestras preguntas, Final-form resultó ser más conveniente.



En general, esta es una gran herramienta para trabajar con formularios. Y junto con algunas reglas para el desarrollo del código base, nos ayudó a optimizar significativamente el desarrollo. La ventaja adicional de todo este trabajo es la capacidad de actualizar más rápido a los nuevos miembros del equipo.



Después de resaltar la lógica, quedó mucho más claro de qué depende un campo en particular: no es necesario leer tres hojas de requisitos en análisis para esto. En estas condiciones, la auditoría de errores ahora lleva al menos dos horas, aunque podrían pasar un par de días antes de todas estas mejoras. Todo este tiempo, el desarrollador estuvo buscando un error fantasma, que no está claro por lo que se manifiesta.



Autores del artículo: Oleg Troshagin, Maxilekt.



PD: Publicamos nuestros artículos en varios sitios de Runet. Suscríbete a nuestras páginas en el canal VK , FB , Instagram o Telegram para conocer todas nuestras publicaciones y otras novedades de Maxilect.



All Articles