En el artículo de hoy me salgo un poco de la costumbre, para reflejar una reflexión sobre un tema que he visto a lo largo de los años y que en mi modesta opinión no siempre mejora nuestro trabajo. Y son las "malditas" modas en el mundo de la programación. Cuántos proyectos han apostado por una moda temporal, y posteriormente han quedado varados en el olvido, intentando mantener la tecnología que describía esa moda como la panacea con esfuerzo, sudor y lágrimas.

Si atendemos a la evolución del desarrollo de software en estos últimos 40 años podemos comprobar como en un inicio se tenían programas que funcionaban con funciones y los llamados procedimientos. Pasados los años se comprobó como los desarrollos al crecer en complejidad era difíciles de mantener y de mejorar. Es por ello que surgió la programación orientada a objetos. Esto trajo algo de estabilidad, pero mucha gente en lugar de formarse y ver como utilizar esta nueva herramienta, prefirió seguir programando "a lo loco", llegando a los mismos problemas que se habían observado en la generación anterior.

Recientemente, con la moda de los hooks en React, he visto que todo el mundo prácticamente te llamaban loco por usar los componentes de clase. Así que raudo y veloz, me he puesto a buscar información de por qué no se deben usar los componentes de clase. Ya que quería comprender qué estaba haciendo mal para poder mejorarlo. Y tras mucho navegar, (ahora es el momento de poner voz de Dross) estas son las siete razones por las que no hay que usar componentes de clase:

  • La programación con clases es confusa para los humanos.
  • La lógica de los métodos del ciclo de vida es difícil de entender.
  • Complejidad con los tests.
  • No hay que preocuparse con el bindeo de métodos y el uso de this.
  • Es fácil compartir la lógica entre componentes.
  • Una muy graciosa: no hay que perder el tiempo pasando de componentes funcionales a componentes de clase.
  • El uso de super en el constructor es confuso.

Vamos a ver pasito a pasito, cada punto.

POO es confusa para los humanos

Seamos sinceros, quien dice eso, es porque no se ha tomado la molestia de querer estudiar un mínimo como funciona la programación orientada a objetos. Es más compleja de interpretar para las máquinas, eso no lo niego, pero acerca mucho más el lenguaje de programación al lenguaje humano. Al final como todo en esta vida, no es que la POO sea la panacea, tiene sus inconvenientes. Pero veo muy vaga la escusa de que resulta confusa para los humanos.

Es más, muchos de los ejemplos encontrados, eran del famoso TODO que añadían complejidad innecesaria componentes de clase, sólo para terminar indicando que las clases quedan muy feas. Realmente, todos los hooks de proyectos reales ¿van a tener 1 o 2 líneas siempre?

La lógica de los métodos del ciclo de vida es difícil de entender

Pues mas o menos tenemos el mismo caso que el anterior. Es más, estos métodos de ciclo de vida ya tienen un nombre muy descriptivo que te indican en qué momento del ciclo de vida se van a ejecutar: componentDidMount, componentWillUnmount, etc. Es decir, sólo hay que usar el o los métodos que necesitamos e introducir el código dentro:

public class MiComponenteClase extends React.PureComponent {
  public componentDidMount(): void {
    // El componente se ha montado
  }
 
  public componentWillUnmount(): void {
    // El componente va a desmontarse
  }
}

function MiComponenteFuncional() {
  useEffect1; // 57


function sumadorf(numero1: number, numero2: number): number {
  return numero1 + numero2;
}

let acumulado: number = 0;
acumulado = sumadorf(acumulado, 12);
console.log(sumadorf(acumulado, 45)); // 57

En el caso anterior, para los mismos paso, la clase también nos va a devolver los mismo resultados. Y sí, es obvio que en una clase muy grande, con muchos estados, se nos pueden ir de madre los tests. Pero en esos casos seguramente, hemos hecho mal y entre otras cosas nos hemos saltado los principios SOLID.

No hay que preocuparse con el bindeo de métodos y el uso de this

La "problemática" con el uso de this en JavaScript es algo que tiene el lenguaje desde sus inicios. Es más, todo desarrollador que se considere senior en JavaScript debería saber como funciona su ámbito de forma obligatoria.

Pues bien, hace unos años no quedaba otra que hacer bind de métodos en JavaScript para no perder el contexto del this, de forma que teníamos código del estilo:

class Componente extends React.PureComponent<IComponenteProps> {
  private onClickMethodBind: () => void;

  public constructor(props: IComponenteProps) {
    super(props);

    // Lanzábamos un bind del método, para que el contexto de this se siguiera manteniendo.
    this.onClickMethodBind = this.onClickMethod.bind(this);
  }

  public render(): JSX.Element {
    return <button onClick={this.onClickMethodBind}>Haz clic</button>
  }

  private onClickMethod(): void {
    alert("Holi");
  }
}

Pero es que ahora tenemos las arrow functions, por lo que ya no es necesario tener atributos con el método "bindeado":

class Componente extends React.PureComponent<IComponenteProps> {
  // Podríamos quitar el constructor si sólo vamos a hacer un super(props). Pero
  // lo dejo por mantener la estructura.
  public constructor(props: IComponenteProps) {
    super(props);
  }

  public render(): JSX.Element {
    return <button onClick={this.onClickMethod}>Haz clic</button>
  }

  private onClickMethod = (): void => {
    alert("Holi");
  }
}

Hala, ya no tenemos problema con el bind.

Es más fácil compartir la lógica entre componentes

Bueno, si seguimos la guía de React, dice que no se use herencia entre componentes. Pero, ya os confirmo que he usado durante años la herencia entre componentes React y funciona perfectamente. Por lo que, cuando hay componentes que requieren usar lógica en común, en mi caso he optado por hacer que hereden de uno base con toda esa lógica común. Con este caso se solventaría el tener una lógica que sepamos que no se comparte entre componentes que no tienen nada que ver.

En el caso de tener operaciones comunes más globales, podemos usar funciones que serán llamadas en nuestros componentes. Como podéis ver en este artículo no demonizo el uso de funciones, sino el que casi nos "fuercen" a usar hooks. Ya que en muchas ocasiones las funciones son más prácticas que las clases. Pero lo dicho, podemos tener funciones en un componente de clase, nadie nos lo impide, y es más, quizás esa función la estemos usando tanto en código React como en código no React. Con JavaScript la mezcla de paradigmas no debería ser un problema.

No hay que perder el tiempo pasando de componentes funcionales a componentes de clase

Aunque sean unas líneas más, todos los componentes que desarrollo siempre son clases. Por ende, si necesito métodos de ciclo de vida no tengo que cambiar el componente. Que sí, que el código es más grande, pero no estamos usando módems de los 90 y no vamos a programar un Horizon Zero Dawn con React. El que ocupe 10KB más un minificado, no va a ser el fin del mundo con las tecnologías actuales, y para eso en todo caso nos deberíamos centrar en las imágenes no escaladas aunque sean miniaturas, vídeos que no permiten cambiar el bitrate, etc.

El uso de super en el constructor es confuso

Llamar a la clase padre desde la hija es de primero de POO. Es decir, es algo lógico para quien programa con POO, y que se aprende enseguida. No entiendo por qué se define que usar algo estándar de POO es un problema, es como decir que declarar las funciones usando la palabra function, es confuso.

Conclusión

Tras mucho leer, no he visto razones de peso reales para tener que dejar atrás el uso de componentes de clase en React. Y es que ahora está de moda hacer todo de forma funcional, demonizando la programación orientada a objetos. Pero claro, lo primero es que la programación funcional que se suele indicar, no es funcional pura (no debería haber bucles, variables, etc). Pero por otro lado, la llamémosla programación funcional "híbrida", y la programación orientada a objetos, pueden convivir y usarse según la situación. Ya que el verdadero potencial de un programador o una programadora es saber cuándo usar todas las herramientas de que dispone. Es por ello que cuando alguien indica que hay que utilizar una tecnología por encima de otra, nos debe dar razones de peso, y no escusas vagas.

En este trabajo, no se aprende de la mañana a la noche, hay que explorar muchas vías, y estudiar mucho durante toda la vida. Es más, el objetivo de este trabajo es tener un código breve, pero que a su vez entendible por tus compañeros y compañeras, y que además sea fácil de mejorar y corregir si es así, escojas el camino que escojas, vas en el buen camino. Pero por otro lado tengo la sensación de que hay modas que se nos imponen, y con el paso de los años no dejarán de ser más que eso, modas.


  1. ) => { console.log("El componente se ha montado"); return () => { console.log("El componente va a desmontarse"); }; }); }

    En el ejemplo anterior, el método te dice en qué momento se va a ejecutar, y ni siquiera hay que aprendérselos de memoria, que para eso estamos en 2022 y tenemos IDEs modernos. En el segundo caso, debes recordar que el código antes del return actúa al montarse y repintarse el componente, y que luego debes devolver otra función que se ejecutará al desmontarse. Que sí, que es obvio que cuando lo hagas dos veces te acordarás, pero sinceramente ¿qué resulta más legible para un humano?

    Otros ejemplos que he visto ponían código duplicado en varios métodos del ciclo de vida, y luego ponían la versión comparada con el hook, y claro, me hacían pensar ¿por qué no ha sacado quien ha hecho el ejemplo, ese código repetido a una función? El que usemos componentes de clase, no implica que todo lo que usemos sean métodos.

    Complejidad con los tests

    Entiendo que con esto se refieren a que un test de una función es más sencillo porque debería siempre devolver el mismo resultado. Pero si hacemos test de una clase (excepto en caso especiales), para un mismo orden de llamadas y unos mismos parámetros también terminará teniendo siempre un mismo resultado. Veamos el ejemplo más tonto del mundo:

    class Sumador {
      private acumulado: number = 0;
    
      public sumar(numero: number): number {
        this.acumulado += number;
        return this.acumulado;
      }
    }
    
    const sumadorc: Sumador = new Sumador();
    sumadorc.sumar(12);
    console.log(sumadorc.sumar(45 

Comparte este artículo con quien quieras
Breve: Sección de bibliografía
Aprendiendo a usar JavaScript Proxy

Leave a Comment

Your email address will not be published. Required fields are marked *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.