En proyectos React pequeños, mantener todos los métodos de un componente en el propio componente es buena idea. En proyectos medianos, desearás poder sacar esos métodos del componente y meterlos en un ‘Utilidades’. Aquí veremos como usar una Clase (en lugar de exportar funciones y variables) para organizar el código.
Nota: este ejemplo se realizará con React.
Refactorización típica
En una refactorización típica, sacaríamos una función de un componente para llevarlo a un ‘Utils’
De:
1 2 3 4 5 6 7 8 9 |
const MyComponent = () => { const someFunction = () => 'Hey, I am text' return ( <div> {someFunction()} </div> ) } |
A:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { someFunction } from 'functionHelper.js' const MyComponent = () => { return ( <div> {someFunction()} </div> ) Y: export const someFunction = () => 'Hey, I am text' Este ejemplo es muy básico, pero veamos la idea: 1. Coge las funciones y copialas en un fichero aparte. 2. Impórtalas e invócalas como se hace normalmente. Pero, cuando las cosas se complican, tendrás que pasar mucho código a esas funciones: objetos, funciones para manejar el estado, etc. Hoy me he encontrado con un problema en el que quería extraer esas funciones del componente y todos requerían los mismos inputs (un <code>resource</code> y una función para actualizarlo). Tiene que haber un modo mejor... |
Refactorizando con una clase
He realizado una demostración para esta entrada. Puedes ver el código en GitHub. El commit inicial muestra toda la funcionalidad dentro del componente principal (App.js
) y los siguientes commits refactorizan el código para usar una clase. Puedes ejecutarlo y probarlo tú mismo. Recuerda usar yarn install
. Comenzamos con un componente que recupera un objeto (imitando el modo en el que lo haríamos desde una API) con varios atributos: repeat (número de cajas), side (altura y anchura), text y color. Después, tenemos varios modos de manipular la vista – cambiando el color, modificando el texo, etc. Después cada cambio, mostramos un mensaje. Por ejemplo, este es nuestro método para cambiar altura y anchura:
1 2 3 4 5 |
changeSide = side => { const obj = {...this.state.obj, side} this.fetchObject(obj); this.setState({ message: `You changed the sides to ${side} pixels!` }); } |
Podemos tener varios métodos que requieren acciones similares, o tal vez métodos muy diferentes. Podemos empezar a pensar en sacarlos a un archivo. Entonces crearíamos un método diferente para llamar a la acción setState
y tendríamos que pasárselo, this.fetchObject
, el objeto en el estado, y el side
que obtenemos como parámetro del método. Si tenemos varios métodos similares, son muchos parámetros a pasar y ya no es tan útiil (ni legible).
En su lugar podemos usar una clase, con su propio constructor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
export default class ObjectManipulator { constructor( { object, fetchObject, markResettable, updateMessage, updateStateValue } ) { this.fetchObject = fetchObject; this.markResettable = markResettable; this.updateMessage = updateMessage; this.updateStateValue = updateStateValue; } changeSide = ( object, side ) => { const newObject = { ...object, side }; this.fetchObject(newObject); this.updateMessage(`You changed the sides to ${side} pixels!`); this.markResettable(); this.updateStateValue('side', side); }; }; |
Esto nos permite crear un objeto cuyas funciones podemos llamar dentro de nuestro componente principal.
1 2 3 4 5 6 7 |
const manipulator = new ObjectManipulator({ object, fetchObject: this.fetchObject, markResettable: this.markResettable, updateMessage: this.updateMessage, updateStateValue: this.updateStateValue, }); |
Esto crea un objeto manipulator
, una instancia de nuestra clase ObjectManipulator
. Cuando llamamos a manipulator.changeSide(object, '800')
ejecutará el método changeSide
definido arriba. No es necesario pasar updateMessage
o cualquier otro método, lo llamamos desde el constructor, cuando hemos creado la instancia.
Como puedes imaginar, esto es muy útil si tenemos gran cantidad de estos métodos. En mi caso, debo llamar a then(res => myFunction(res)
después de todo lo que he intentado extraer. Definir myFunction
en la instancia en lugar de pasarla a cada función me ha ahorrado mucho código.
Manteniéndolo todo organizado
Este método de organización puede ser muy útil para mantenerlo todo en su lugar. Por ejemplo, tengo un array de colores que mapeamos para obtener los botones de colores que vemos en el ejemplo. Moviendo esta constante a ObjectManipulator
nos aseguramos que no ‘chocan’ con otros colors
de mi aplicación:
1 2 3 4 5 |
export default class ObjectManipulator { [...] colors = ['blue', 'red', 'orange', 'aquamarine', 'green', 'gray', 'magenta']; }; |
Podemos usar manipulator.colors
para recoger los colores correctos para cada página, y puede coexistir con una variable global colors que usamos para cualquier otra funcionalidad.
Referencias
Good old Mozilla Class docs (en inglés)
Nota: puedes encontrar el artículo original en https://medium.freecodecamp.org/javascript-code-cleanup-how-you-can-refactor-to-use-classes-3948118e4468