“Um módulo deve ser responsável por um, e apenas um, ator.” - Wikipédia.
// ❌ Bad Practice: Component with Multiple Responsibilities const Products = () => { return ( <div className="products"> {products.map((product) => ( <div key={product?.id} className="product"> <h3>{product?.name}</h3> <p>${product?.price}</p> </div> ))} </div> ); };
No exemplo acima, o componente Products
viola o Princípio de Responsabilidade Única ao assumir múltiplas responsabilidades. Ele gerencia a iteração de produtos e lida com a renderização da interface do usuário para cada produto. Isso pode tornar o componente difícil de entender, manter e testar no futuro.
// ✅ Good Practice: Separating Responsibilities into Smaller Components import Product from './Product'; import products from '../../data/products.json'; const Products = () => { return ( <div className="products"> {products.map((product) => ( <Product key={product?.id} product={product} /> ))} </div> ); }; // Product.js // Separate component responsible for rendering the product details const Product = ({ product }) => { return ( <div className="product"> <h3>{product?.name}</h3> <p>${product?.price}</p> </div> ); };
“entidades de software (classes, módulos, funções, etc.) devem ser abertas para extensão, mas fechadas para modificação.” - Wikipédia.
// ❌ Bad Practice: Violating the Open-Closed Principle // Button.js // Existing Button component const Button = ({ text, onClick }) => { return ( <button onClick={onClick}> {text} </button> ); } // Button.js // Modified Existing Button component with additional icon prop (modification) const Button = ({ text, onClick, icon }) => { return ( <button onClick={onClick}> <i className={icon} /> <span>{text}</span> </button> ); } // Home.js // 👇 Avoid: Modified existing component prop const Home = () => { const handleClick= () => {}; return ( <div> {/* ❌ Avoid this */} <Button text="Submit" onClick={handleClick} icon="fas fa-arrow-right" /> </div> ); }
No exemplo acima, modificamos o componente Button
existente adicionando uma propriedade icon
. Alterar um componente existente para acomodar novos requisitos viola o Princípio Aberto-Fechado. Essas mudanças tornam o componente mais frágil e introduzem o risco de efeitos colaterais não intencionais quando usados em diferentes contextos.
// ✅ Good Practice: Open-Closed Principle // Button.js // Existing Button functional component const Button = ({ text, onClick }) => { return ( <button onClick={onClick}> {text} </button> ); } // IconButton.js // IconButton component // ✅ Good: You have not modified anything here. const IconButton = ({ text, icon, onClick }) => { return ( <button onClick={onClick}> <i className={icon} /> <span>{text}</span> </button> ); } const Home = () => { const handleClick = () => { // Handle button click event } return ( <div> <Button text="Submit" onClick={handleClick} /> {/* <IconButton text="Submit" icon="fas fa-heart" onClick={handleClick} /> </div> ); }
No exemplo acima, criamos um componente funcional IconButton
separado. O componente IconButton
encapsula a renderização de um botão de ícone sem modificar o componente Button
existente. Ele adere ao Princípio Aberto-Fechado, estendendo a funcionalidade por meio da composição em vez da modificação.
“Objetos de subtipo devem ser substituíveis por objetos de supertipo” — Wikipedia.
// ⚠️ Bad Practice // This approach violates the Liskov Substitution Principle as it modifies // the behavior of the derived component, potentially resulting in unforeseen // problems when substituting it for the base Select component. const BadCustomSelect = ({ value, iconClassName, handleChange }) => { return ( <div> <i className={iconClassName}></i> <select value={value} onChange={handleChange}> <options value={1}>One</options> <options value={2}>Two</options> <options value={3}>Three</options> </select> </div> ); }; const LiskovSubstitutionPrinciple = () => { const [value, setValue] = useState(1); const handleChange = (event) => { setValue(event.target.value); }; return ( <div> {/** ❌ Avoid this */} {/** Below Custom Select doesn't have the characteristics of base `select` element */} <BadCustomSelect value={value} handleChange={handleChange} /> </div> );
No exemplo acima, temos um componente BadCustomSelect
destinado a servir como uma entrada de seleção personalizada no React. No entanto, ele viola o Princípio de Substituição de Liskov (LSP) porque restringe o comportamento do elemento select
base.
// ✅ Good Practice // This component follows the Liskov Substitution Principle and allows the use of select's characteristics. const CustomSelect = ({ value, iconClassName, handleChange, ...props }) => { return ( <div> <i className={iconClassName}></i> <select value={value} onChange={handleChange} {...props}> <options value={1}>One</options> <options value={2}>Two</options> <options value={3}>Three</options> </select> </div> ); }; const LiskovSubstitutionPrinciple = () => { const [value, setValue] = useState(1); const handleChange = (event) => { setValue(event.target.value); }; return ( <div> {/* ✅ This CustomSelect component follows the Liskov Substitution Principle */} <CustomSelect value={value} handleChange={handleChange} defaultValue={1} /> </div> ); };
No código revisado, temos um componente CustomSelect
destinado a estender a funcionalidade do elemento select
padrão no React. O componente aceita props como value
, iconClassName
, handleChange
e props adicionais usando o operador spread ...props
. Ao permitir o uso das características do elemento select
e aceitar props adicionais, o componente CustomSelect
segue o Princípio de Substituição de Liskov (LSP).
“Nenhum código deve ser forçado a depender de métodos que não usa.” - Wikipédia.
// ❌ Avoid: disclose unnecessary information for this component // This introduces unnecessary dependencies and complexity for the component const ProductThumbnailURL = ({ product }) => { return ( <div> <img src={product.imageURL} alt={product.name} /> </div> ); }; // ❌ Bad Practice const Products = ({ product }) => { return ( <div> <ProductThumbnailURL product={product} /> <h4>{product?.name}</h4> <p>{product?.description}</p> <p>{product?.price}</p> </div> ); }; const Products = () => { return ( <div> {products.map((product) => ( <Product key={product.id} product={product} /> ))} </div> ); }
No exemplo acima, passamos todos os detalhes do produto para o componente ProductThumbnailURL
, mesmo que isso não seja necessário. Acrescenta riscos e complexidade desnecessários ao componente e viola o Princípio de Segregação de Interface (ISP).
// ✅ Good: reducing unnecessary dependencies and making // the codebase more maintainable and scalable. const ProductThumbnailURL = ({ imageURL, alt }) => { return ( <div> <img src={imageURL} alt={alt} /> </div> ); }; // ✅ Good Practice const Products = ({ product }) => { return ( <div> <ProductThumbnailURL imageURL={product.imageURL} alt={product.name} /> <h4>{product?.name}</h4> <p>{product?.description}</p> <p>{product?.price}</p> </div> ); }; const Products = () => { return ( <div> {products.map((product) => ( <Product key={product.id} product={product} /> ))} </div> ); };
No código revisado, o componente ProductThumbnailURL
recebe apenas as informações necessárias em vez de todos os detalhes do produto. Previne riscos desnecessários e promove o Princípio de Segregação de Interface (ISP).
“Uma entidade deve depender de abstrações, não de concreções” – Wikipedia.
// ❌ Bad Practice // This component follows concretion instead of abstraction and // breaks Dependency Inversion Principle const CustomForm = ({ children }) => { const handleSubmit = () => { // submit operations }; return <form onSubmit={handleSubmit}>{children}</form>; }; const DependencyInversionPrinciple = () => { const [email, setEmail] = useState(); const handleChange = (event) => { setEmail(event.target.value); }; const handleFormSubmit = (event) => { // submit business logic here }; return ( <div> {/** ❌ Avoid: tightly coupled and hard to change */} <BadCustomForm> <input type="email" value={email} onChange={handleChange} name="email" /> </BadCustomForm> </div> ); };
O componente CustomForm
é fortemente acoplado a seus filhos, impedindo a flexibilidade e dificultando a alteração ou extensão de seu comportamento.
// ✅ Good Practice // This component follows abstraction and promotes Dependency Inversion Principle const AbstractForm = ({ children, onSubmit }) => { const handleSubmit = (event) => { event.preventDefault(); onSubmit(); }; return <form onSubmit={handleSubmit}>{children}</form>; }; const DependencyInversionPrinciple = () => { const [email, setEmail] = useState(); const handleChange = (event) => { setEmail(event.target.value); }; const handleFormSubmit = () => { // submit business logic here }; return ( <div> {/** ✅ Use the abstraction instead */} <AbstractForm onSubmit={handleFormSubmit}> <input type="email" value={email} onChange={handleChange} name="email" /> <button type="submit">Submit</button> </AbstractForm> </div> ); };
No código revisado, introduzimos o componente AbstractForm
, que atua como uma abstração para o formulário. Ele recebe a função onSubmit
como prop e manipula o envio do formulário. Essa abordagem nos permite trocar ou estender facilmente o comportamento do formulário sem modificar o componente de nível superior.
Fique curioso; continue codificando!
Referência:
Publicado também .