Composición de componentes en React JS

Después de 2 años de trabajar con React, tengo una experiencia que me gustaría compartir. Si acaba de comenzar a dominar React, espero que este artículo lo ayude a elegir el camino correcto para desarrollar un proyecto de 1-5 cinco formularios a un gran conjunto de componentes y al mismo tiempo no confundirse.



Si ya es un profesional, quizás recuerde sus muletas. O quizás ofrecer mejores soluciones a los problemas descritos.



Este artículo se centrará en mi opinión personal sobre cómo organizar la composición de los componentes.

Comencemos con algo pequeño



Consideremos alguna forma abstracta. Queremos decir que hay muchos campos en el formulario (alrededor de 10-15 piezas), pero para que los ojos no se suban, tomaremos un formulario con 4 campos como ejemplo.



Un objeto multinivel del siguiente tipo llega a la entrada del componente:



const unit = {
  name: 'unit1',
  color: 'red',
  size: {
    width: 2,
    height: 4,
  },
}


Un desarrollador sin experiencia (como yo en el primer mes de trabajo con react) hará todo esto en un componente donde los valores de entrada se almacenarán en el estado:



const Component = ({ values, onSave, onCancel }) => {
  const [ state, setState ] = useState({});

  useEffect(() => {
    setState(values);
  }, [ values, setState ]);

  return <div className="form-layout">
    <div className="form-field">
      <Input onChange={({ target: { value } }) =>
        setState((state) => ({...state, name: value }))
      }/>
    </div>
    <div className="form-field">
      <Input onChange={({ target: { value } }) =>
        setState((state) => ({...state, color: value }))
      }/>
    </div>
    <div className="size">
      <div className="form-field">
        <Input onChange={({ target: { value } }) =>
          setState((state) => ({...state, size: { width: value } }))
        }/>
      </div>
      <div className="form-field">
        <Input onChange={({ target: { value } }) =>
          setState((state) => ({...state, size: { height: value } }))
        }/>
      </div>
    </div>
    <div className="buttons">
      <Button onClick={() => onSave(state)}>Save</Button>
      <Button onClick={() => onCancel()}>Cancel</Button>
    </div>
  </div>
}


Al ver lo rápido que el desarrollador lo manejó, el cliente ofrecerá hacer otro basado en este formulario, pero sin el bloque "tamaño".



Hay 2 opciones (y ambas no son correctas):



  1. Puede copiar el primer componente y agregar lo que falta o eliminar el exceso. Esto generalmente se hace cuando no toman su propio componente como base y tienen miedo de romper algo en él.
  2. Agregue configuraciones de componentes adicionales a los parámetros.


Si después de la implementación de 3-5 formularios el proyecto finaliza, entonces el desarrollador está de suerte.



Pero, por lo general, esto es solo el comienzo y la cantidad de mohos diferentes solo está creciendo.



Entonces se requiere uno similar, pero sin el bloque "color".

Luego uno similar, pero con un nuevo bloque de "descripción".

Luego haga algunos bloques solo para leer.

Luego, una forma similar debe insertarse en otra forma, en general, tristeza, que a veces surge de esto.



Nuevos formularios copiando



Un desarrollador que adopte un enfoque de copia, por supuesto, implementará rápidamente nuevos formularios. Aunque habrá menos de 10. Pero luego el estado de ánimo disminuirá gradualmente.



Especialmente cuando ocurre un rediseño. Corrija las sangrías entre los bloques del formulario "un poco", cambie el componente de selección de color. Después de todo, no se puede prever todo a la vez y muchas soluciones de diseño deberán revisarse después de su implementación.



Es importante prestar atención a la frecuente mención de "forma similar". Después de todo, el producto es uno y todos los moldes deben ser similares. Como resultado, debe lidiar con un trabajo rutinario y poco interesante para volver a trabajar lo mismo en cada formulario y, por cierto, los evaluadores también deberán verificar cada formulario.

En general, entiendes la idea. No copie componentes similares.


Nuevas formas por generalización



Si el desarrollador ha elegido el segundo camino, entonces, por supuesto, está a caballo aquí, podría pensar. Tiene solo unos pocos componentes que pueden dibujar docenas de formas. Corrija la sangría a lo largo del proyecto o cambie el componente de "color"; esto es para corregir dos líneas en el código y el probador tendrá que verificar dos veces en solo un par de lugares.



Pero de hecho, este camino dio lugar a un componente muy difícil de mantener.



Es difícil de usar, porque muchos parámetros, algunos se llaman casi iguales, para comprender de qué es responsable cada parámetro, debe ir al interior.



<Component
  isNameVisible={true}
  isNameDisabled={true}
  nameLabel="Model"
  nameType="input"
  isColorVisible={true}
  isColorDisabled={false}
  colorType={'dropdown'}
  isSizeVisible={true}
  isHeightVisible={true}
  isWidthDisabled={false}
/>


También es difícil de mantener. Como regla general, existen complejas condiciones entrelazadas en el interior y agregar una nueva condición puede romper todo lo demás. Ajustar el componente para generar una forma puede romper el resto.

En general, entiendes la idea. No haga que el componente tenga muchas propiedades.
Para resolver los problemas de la segunda opción, ¿los desarrolladores comienzan con qué? Correcto. Como desarrolladores reales, comienzan a desarrollar algo que facilita la configuración de un componente complejo.

Por ejemplo, hacen el parámetro fields (bueno, como columnas en react-table). Y allí se pasan los parámetros de los campos: qué campo es visible, cuál no es editable, el nombre del campo.



La llamada al componente se convierte en esto:



const FIELDS = {
    name: { visible: true, disabled: true, label: 'Model', type: 'input' },
    color: { visible: true, disabled: false, type: 'dropdown' },
    size: { visible: true },
    height: { visible: true },
    width: { disabled: false },
}
<Component
  values={values}
  fields={FIELDS}
/>


Como resultado, el desarrollador está orgulloso de sí mismo. Generalizó la configuración para todos los campos y optimizó el código interno del componente: ahora para cada campo se llama una función, que convierte la configuración en accesorios del componente correspondiente. Incluso por el nombre del tipo, se representa un componente diferente. Un poco más y obtendrás tu propio marco.



¿Frio? Demasiado.



Con suerte, no se convierte en esto:



const FIELDS = {
    name: getInputConfig({ visible: true, disabled: true, label: 'Model'}),
    color: getDropDownConfig({ visible: true, disabled: false}),
    size: getBlockConfig({ visible: true }),
    height: getInputNumberConfig({ visible: true }),
    width: getInputNumberConfig({ disabled: false }),
}
<Component
  values={values}
  fields={FIELDS}
/>


En general, entiendes la idea. No reinventes la rueda.


Nuevas formas mediante la composición de componentes y formas anidadas



Recordemos sobre lo que todavía escribimos. Ya tenemos una biblioteca de reacciones. No es necesario inventar nuevos diseños. La configuración de componentes en reaccionar se describe mediante el marcado JSX




const Form1 = ({ values }) => {
  return <FormPanel>
    <FormField disabled label=”Model”>
      <Input name="name" />
    </FormField>
    <FormField disabled label=”Color”>
      <DropDown name="color" />
    </FormField>
    <FormPanel>
      <FormField disabled label="Height">
        <Input name="height" />
      </FormField>
      <FormField disabled label="Width">
        <Input name="width" />
     </From Field>
    </FormPanelt>
  </FormPanel>
}


Parece que volvemos a la primera opción de copia. Pero en realidad no. Esta es una composición que elimina los problemas de los dos primeros enfoques.



Hay un conjunto de ladrillos a partir de los cuales se ensambla la forma. Cada ladrillo es responsable de algo diferente. Algunos por el diseño y la apariencia, otros por la entrada de datos.



Si necesita cambiar las sangrías en todo el proyecto, entonces es suficiente con hacerlo en el componente FormField. Si necesita cambiar el funcionamiento de la lista desplegable, esto se hace en un lugar en el componente DropDown.



Si necesita una forma similar, pero por ejemplo, para que no haya un campo de "color", entonces colocamos los bloques comunes en ladrillos separados y ensamblamos otra forma.



Movemos el bloque Tamaño a un componente separado:



const Size = () =>  <FormPanel>
    <FormField disabled label="Height">
      <Input name="height" />
    </FormField>
    <FormField disabled label=”Width”>
      <Input name="width" />
   </From Field>
  </FormPanel>


Hacer una forma con una variedad de colores:



const Form1 = () => <FormPanel>
    <FormField disabled label="Color">
      <DropDown name="color" />
   </FormField>
    <FormField disabled label="Model">
       <Input name="name" />
    </FormField>
    <Size name="size" />
</FormPanel>


Hacemos una forma similar, pero sin elegir color:



const Form2 = () => <FormPanel>
    <FormField disabled label="Model">
       <Input name="name" />
    </FormField>
    <Size name="size" />
</FormPanel>


Lo más importante es que la persona que obtiene dicho código no necesita lidiar con las configuraciones ficticias del predecesor. Todo está escrito en JSX, familiar para cualquier desarrollador de react, con sugerencias de parámetros para cada componente.

En general, entiendes la idea. Utilice JSX y composición de componentes.


Algunas palabras sobre el estado



Ahora prestemos atención al estado. Más precisamente, su ausencia. Tan pronto como agregamos un estado, cerramos el flujo de datos y se vuelve más difícil reutilizar el componente. Todos los ladrillos deben ser apátridas (es decir, apátridas). Y solo en el nivel superior, la forma ensamblada a partir de ladrillos se puede conectar al estado. Si el formulario es complejo, entonces tiene sentido dividirlo en varios contenedores y conectar cada parte a redux.



No sea perezoso para crear un componente de formulario sin estado independiente. Luego, tendrá la oportunidad de usarlo como parte de otro formulario, o hacer un formulario completo basado en él, o un contenedor para conectarse a redux.



Por supuesto, en los ladrillos puede haber estados para almacenar estados internos no relacionados con el flujo de datos general. Por ejemplo, en el estado interno DropDown (lista desplegable) es conveniente almacenar el atributo tanto si está expandido como si no.

En general, entiendes la idea. Separe los componentes en Stateless y Statefull.


Total



Sorprendentemente, encuentro periódicamente todos los errores descritos en el artículo y los problemas que surgen de ellos. Espero que no los repita y luego mantener su código sea mucho más fácil.



Repetiré los puntos principales:



  1. No copie componentes similares. Utilice el principio SECO.
  2. No cree componentes con muchas propiedades y funcionalidad. Cada componente debe ser responsable de algo diferente (responsabilidad única de SOLID)
  3. Separe los componentes en Stateless y Statefull.
  4. No reinventes tus diseños. Utilice JSX y la composición de sus componentes.



All Articles