„Ein Modul sollte einem und nur einem Akteur gegenüber verantwortlich sein.“ – Wikipedia.
// ❌ 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> ); };
Im obigen Beispiel verstößt die Products
gegen das Prinzip der Einzelverantwortung, indem sie mehrere Verantwortlichkeiten übernimmt. Es verwaltet die Iteration von Produkten und kümmert sich um das UI-Rendering für jedes Produkt. Dies kann es schwierig machen, die Komponente in Zukunft zu verstehen, zu warten und zu testen.
// ✅ 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> ); };
„Softwareeinheiten (Klassen, Module, Funktionen usw.) sollten für Erweiterungen offen, für Änderungen jedoch geschlossen sein.“ – Wikipedia.
// ❌ 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> ); }
Im obigen Beispiel ändern wir die vorhandene Button
Komponente, indem wir eine icon
Requisite hinzufügen. Die Änderung einer vorhandenen Komponente zur Anpassung an neue Anforderungen verstößt gegen das Open-Closed-Prinzip. Diese Änderungen machen die Komponente anfälliger und bergen das Risiko unbeabsichtigter Nebenwirkungen bei der Verwendung in verschiedenen Kontexten.
// ✅ 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> ); }
Im obigen Beispiel erstellen wir eine separate IconButton
Funktionskomponente. Die IconButton
Komponente kapselt die Darstellung einer Symbolschaltfläche, ohne die vorhandene Button
Komponente zu ändern. Es folgt dem Open-Closed-Prinzip, indem es die Funktionalität durch Komposition und nicht durch Modifikation erweitert.
„Subtyp-Objekte sollten durch Supertyp-Objekte ersetzbar sein“ – 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> );
Im obigen Beispiel haben wir eine BadCustomSelect
Komponente, die als benutzerdefinierte Auswahleingabe in React dienen soll. Es verstößt jedoch gegen das Liskov-Substitutionsprinzip (LSP), da es das Verhalten des select
einschränkt.
// ✅ 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> ); };
Im überarbeiteten Code haben wir eine CustomSelect
Komponente, die die Funktionalität des Standard- select
Elements in React erweitern soll. Die Komponente akzeptiert Requisiten wie value
, iconClassName
, handleChange
und zusätzliche Requisiten mithilfe des Spread-Operators ...props
. Indem die CustomSelect
Komponente die Verwendung der Eigenschaften des select
Elements zulässt und zusätzliche Requisiten akzeptiert, folgt sie dem Liskov-Substitutionsprinzip (LSP).
„Kein Code sollte gezwungen werden, von Methoden abhängig zu sein, die er nicht verwendet.“ – Wikipedia.
// ❌ 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 Product = ({ 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> ); }
Im obigen Beispiel übergeben wir die gesamten Produktdetails an die ProductThumbnailURL
Komponente, obwohl diese nicht erforderlich ist. Es fügt der Komponente unnötige Risiken und Komplexität hinzu und verstößt gegen das Interface Segregation Principle (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 Product = ({ 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> ); };
Im überarbeiteten Code erhält die ProductThumbnailURL
Komponente nur die erforderlichen Informationen statt der gesamten Produktdetails. Es verhindert unnötige Risiken und fördert das Interface Segregation Principle (ISP).
„Eine Entität sollte auf Abstraktionen beruhen, nicht auf Konkretionen“ – 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> ); };
Die CustomForm
Komponente ist eng mit ihren untergeordneten Komponenten verknüpft, was Flexibilität verhindert und es schwierig macht, ihr Verhalten zu ändern oder zu erweitern.
// ✅ 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> ); };
Im überarbeiteten Code führen wir die AbstractForm
Komponente ein, die als Abstraktion für das Formular fungiert. Es empfängt die Funktion onSubmit
als Requisite und übernimmt die Formularübermittlung. Dieser Ansatz ermöglicht es uns, das Formularverhalten einfach auszutauschen oder zu erweitern, ohne die übergeordnete Komponente zu ändern.
Bleib neugierig; Codieren Sie weiter!
Referenz:
Auch veröffentlicht.