Aplicando principios SOLID en React
June 20, 2023

En el mundo del desarrollo de software, siempre buscamos seguir buenas pr谩cticas y principios para crear aplicaciones escalables, mantenibles y robustas. Uno de los conjuntos de principios m谩s conocidos y ampliamente adoptados es SOLID, propuesto por Robert C. Martin. Estos principios ayudan a los desarrolladores a dise帽ar c贸digo de alta calidad y f谩cil de mantener. En este art铆culo, exploraremos c贸mo aplicar los principios SOLID en el contexto de React y proporcionaremos ejemplos de c贸mo aplicarlos en componentes funcionales tipados.
Principio de Responsabilidad 脷nica (SRP)
Un componente debe tener una 煤nica responsabilidad y prop贸sito. Al utilizar componentes funcionales y TypeScript, podemos lograr f谩cilmente este principio.
// Antes: Un componente que maneja la entrada de texto y la validaci贸n
const InputWithValidation: React.FC = () => {
/* c贸digo de validaci贸n y renderizado */
}
// Despu茅s: Separar en dos componentes
const TextInput: React.FC = () => {
/* solo renderizado de entrada de texto */
}
const InputValidation: React.FC = () => {
/* solo validaci贸n */
}
Principio de abierto / cerrado (OCP)
Los componentes deben estar abiertos para extenderse pero cerrados para modificarse. Esto significa que no debe ser necesario modificar un componente existente para extenderlo. En su lugar, podemos crear un nuevo componente que extienda el comportamiento del componente existente.
// Antes: Un componente cerrado para modificaci贸n
// Button.tsx
import React from 'react';
interface ButtonProps {
onClick: () => void;
label: string;
}
const Button: React.FC<ButtonProps> = ({ onClick, label }) => {
return (
<button onClick={onClick}>
{label}
</button>
);
};
export default Button;
// Despu茅s: Un componente abierto para extensi贸n
import React from 'react';
import Button from './Button';
interface IconButtonProps extends ButtonProps {
icon: string;
}
const IconButton: React.FC<IconButtonProps> = ({ onClick, label, icon }) => {
return (
<Button onClick={onClick} label={label}>
<span className="icon">{icon}</span>
</Button>
);
};
export default IconButton;
Principio de sustituci贸n de Liskov (LSP)
Los componentes deben poder ser reemplazados por sus subtipos sin alterar el comportamiento del programa. Esto significa que los componentes deben ser intercambiables con sus subtipos sin afectar el comportamiento de la aplicaci贸n. En el caso de componentes funcionales y TypeScript, podemos lograr esto utilizando la composici贸n en lugar de la herencia.
// Componente base
interface ButtonProps {
className?: string;
}
const Button: React.FC<ButtonProps> = (props) => {
/* c贸digo del componente base */
}
// Componente derivado
const IconButton: React.FC<ButtonProps> = (props) => {
return <Button {...props} />;
}
Principio de segregaci贸n de interfaz (ISP)
Una entidad no debe verse obligada a depender de interfaces que no utiliza. En React y TypeScript, esto se traduce en no pasar props innecesarias a los componentes.
// Antes: Pasar props innecesarias
interface Person {
name: string;
age: number;
address: string;
}
interface PersonComponentProps {
person: Person;
}
function PersonComponent(props: PersonComponentProps) {
return (
<div>
<div>{props.person.name}</div>
<div>{props.person.age}</div>
</div>
);
}
// Despu茅s: Pasar solo las props necesarias
interface PersonComponentProps {
name: string;
age: number;
}
function PersonComponent(props: PersonComponentProps) {
return (
<div>
<div>{props.name}</div>
<div>{props.age}</div>
</div>
);
}
Principio de inversi贸n de dependencia (DIP)
Los componentes de alto nivel no deben depender de los componentes de bajo nivel. Ambos deben depender de abstracciones. En React y TypeScript, esto significa que los componentes de alto nivel no deben depender de los componentes de bajo nivel. En su lugar, ambos deben depender de abstracciones como interfaces o tipos.
// Antes: Componente de alto nivel depende de componente de bajo nivel
import api from '~/common/api'
const DashboardComponent = () => {
const [data, setData] = useState([]);
useEffect(() => {
api.get('/dashboard').then((response) => {
setData(response.data);
});
}, []);
return (
<div>
{data.map((item) => (
<div>{item.name}</div>
))}
</div>
);
}
// Despu茅s: Ambos componentes dependen de una abstracci贸n
const DashboardComponent = () => {
const { data, isLoading, error } = useFetchData(apiClient.getDashboardInfo);
if (isLoading) {
return <div>Loading...</div>;
}
if (error) {
return <div>Error: {error.message}</div>;
}
return (
<div>
{data.map((item) => (
<div>{item.name}</div>
))}
</div>
);
}