Problema 🤔
En el desarrollo frontend moderno, normalmente se suele utilizar librerías de componentes como MUI, Angular Material, Chakra UI, etc. Estas librerías nos ayudan a ser más rápidos en nuestro desarrollo, pero también tienen sus inconvenientes. Por ejemplo:
- A medida que el proyecto crece y se van utilizando dichos componentes alrededor de toda nuestra aplicación, nos acoplamos más y más a estas mismas librerías. Si en el futuro queremos utilizar otra librería para un componente, muchas son las páginas que habría que tocar para sustituirlo.
- El tiempo también pasa y estas librerías se van deprecando ciertas interfaces y actualizando otras muchas. A quien no le ha pasado que al actualizar una versión de un framework CSS le cambian el nombre a una clase CSS y ya toca cambiar este nombre por toda la aplicación.
- Además, si utilizas varias librerías en tu proyecto, cada vez que quieras saber qué interfaz tiene un componente te toca ir a la documentación oficial de dicha librería para ver qué propiedades tiene y cómo se utilizan. Haciendo que tu código sea difícil de comprender y mantener. ¿Cómo podemos evitar estos problemas y hacer que nuestro código sea más legible, modular y reutilizable? En este artículo, te mostraré cómo puedes crear tus propios componentes personalizados usando las mejores prácticas de desarrollo frontend.
Ejemplo práctico
Para ilustrar el problema y la solución, vamos a usar un ejemplo sencillo. Imagina que tienes que crear una página que liste postres ordenados por sus calorías. Si utilizas MUI, una librería de componentes basada en React, lo primero que tendrás que hacer es importar los distintos componentes que componen la tabla:
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
Luego utilizarás dichos componentes dentro de la página donde quieras la tabla:
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Dessert (100g serving)</TableCell>
<TableCell align="right">Calories</TableCell>
<TableCell align="right">Fat (g)</TableCell>
<TableCell align="right">Carbs (g)</TableCell>
<TableCell align="right">Protein (g)</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow
key={row.name}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell align="right">{row.calories}</TableCell>
<TableCell align="right">{row.fat}</TableCell>
<TableCell align="right">{row.carbs}</TableCell>
<TableCell align="right">{row.protein}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
Como puedes observar, esta tabla es muy flexible y dinámica, pero también tiene algunos inconvenientes:
- Al usar los componentes de MUI directamente en la página, tu código queda acoplado a esta librería y depende de sus cambios y actualizaciones.
- Además, si usas el mismo componente de tabla en distintas páginas para mostrar otras cosas, toda tu aplicación queda acoplada a esta librería, lo que dificulta su mantenimiento y escalabilidad.
Solución 🧐
En esta parte, te voy a explicar cómo puedes aplicar el patrón facade para mejorar la calidad y la mantenibilidad de tu código. El patrón facade es un tipo de patrón de diseño estructural que se usa para simplificar la interacción con un subsistema complejo. Consiste en crear un objeto de fachada que implementa una interfaz sencilla y unifica las distintas interfaces del subsistema o subsistemas. De esta forma, se reduce el acoplamiento entre los clientes y el subsistema, y se facilita su uso y comprensión.
Ejemplo de aplicación
Para ilustrar el uso del patrón facade, vamos a retomar el ejemplo de la tabla que lista postres ordenados por sus calorías. En lugar de utilizar directamente los componentes de MUI en la página que vamos a mostrar al usuario final, vamos a crear un componente personalizado que encapsule la lógica y el estilo de la tabla. Este componente será nuestra fachada, y nos permitirá abstraer la complejidad de los componentes de MUI y desacoplar nuestro código de la librería.
El código de nuestra página quedaría algo así:
import TableComponent from '@lib/componets/data-display/table';
const headElements: HeadElement<Desert>[] = [
{ id: 'name', label: 'Postre' },
{ id: 'calories', label: 'Calorías' },
{ id: 'fat', label: 'Grasa' },
{ id: 'carbs', label: 'Carbohidratos' },
{ id: 'protein', label: 'Proteinas' }
];
const data = [{name: 'Yogur', calories: 200, fat: 100, carbs: 0, protein: 130}];
export default function Page() {
return (
<TableComponent<Desert>
rowKeys={['name', 'calories', 'fat', 'carbs', 'protein']}
rows={data}
headElements={headElements}
/>
)
}
Como puedes ver, el código es mucho más simple y legible. Solo tenemos que importar nuestro componente personalizado TableComponent
y pasarle los datos que queremos mostrar como props. No tenemos que preocuparnos por los detalles internos de los componentes de MUI, ni por sus posibles cambios o actualizaciones.
El código de nuestro componente TableComponent
sería algo así:
// TableComponent.tsx
import React from 'react';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
type HeadElement<T> = {
id: keyof T;
label: string;
};
type Props<T> = {
rowKeys: Array<keyof T>;
rows: Array<T>;
headElements: Array<HeadElement<T>>;
};
function TableComponent<T>(props: Props<T>) {
const { rowKeys, rows, headElements } = props;
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
{headElements.map((headElement) => (
<TableCell key={headElement.id}>{headElement.label}</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow
key={row.name}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
{rowKeys.map((rowKey) => (
<TableCell key={rowKey} align="right">
{row[rowKey]}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
export default TableComponent;
Aquí podemos ver cómo nuestro componente TableComponent
implementa la lógica y el estilo de la tabla usando los componentes de MUI y los props que recibe. Nuestro componente es genérico y puede recibir cualquier tipo de dato como prop, siempre que se especifiquen las claves y los elementos del encabezado. De esta forma, podemos reutilizar nuestro componente en diferentes páginas y proyectos sin depender de la librería MUI.
Conclusiones 🙂
Para terminar, vamos a resumir las ventajas e inconvenientes del patrón facade:
- Ventajas:
- Nos ayuda a desacoplar nuestro proyecto de librerías de terceros.
- Facilita el mantenimiento y la actualización de nuestro código.
- Se puede aplicar a otras dependencias de nuestra aplicación.
- Inconvenientes:
- Nuestra fachada puede convertirse en un objeto todo poderoso (hay que respetar el principio de responsabilidad única) .
- Puede ocultar la complejidad del subsistema y dificultar su depuración.
Espero que este artículo te haya servido para comprender mejor el patrón facade y cómo puedes usarlo para mejorar la calidad y la mantenibilidad de tu código. Si tienes alguna duda o comentario, no dudes en contactarme. 😊
Top comments (0)