“Bir modül tek ve tek bir aktöre karşı sorumlu olmalıdır.” — Vikipedi.
// ❌ 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> ); };
Yukarıdaki örnekte Products
bileşeni, birden fazla sorumluluk üstlenerek Tek Sorumluluk İlkesini ihlal etmektedir. Ürünlerin yinelenmesini yönetir ve her ürün için kullanıcı arayüzü oluşturma işlemini gerçekleştirir. Bu, bileşenin gelecekte anlaşılmasını, bakımını ve test edilmesini zorlaştırabilir.
// ✅ 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> ); };
“Yazılım varlıkları (sınıflar, modüller, işlevler vb.) genişletmeye açık, değişiklik yapmaya kapalı olmalıdır.” — Vikipedi.
// ❌ 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> ); }
Yukarıdaki örnekte, mevcut Button
bileşenini bir icon
prop ekleyerek değiştiriyoruz. Mevcut bir bileşeni yeni gereksinimleri karşılayacak şekilde değiştirmek Açık-Kapalı İlkesini ihlal eder. Bu değişiklikler bileşeni daha kırılgan hale getirir ve farklı bağlamlarda kullanıldığında istenmeyen yan etki riskini ortaya çıkarır.
// ✅ 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> ); }
Yukarıdaki örnekte ayrı bir IconButton
fonksiyonel bileşeni oluşturuyoruz. IconButton
bileşeni, mevcut Button
bileşenini değiştirmeden bir simge düğmesinin oluşturulmasını kapsar. İşlevselliği değişiklik yerine kompozisyon yoluyla genişleterek Açık-Kapalı Prensibine bağlı kalır.
"Alt tip nesneler, süper tip nesnelerin yerine geçebilmelidir" - Vikipedi.
// ⚠️ 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> );
Yukarıdaki örnekte, React'te özel seçim girişi olarak hizmet etmesi amaçlanan bir BadCustomSelect
bileşenimiz var. Ancak temel select
öğesinin davranışını kısıtladığı için Liskov Değiştirme İlkesini (LSP) ihlal eder.
// ✅ 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> ); };
Revize edilen kodda, React'teki standart select
öğesinin işlevselliğini genişletmeyi amaçlayan bir CustomSelect
bileşenimiz var. Bileşen, value
, iconClassName
, handleChange
gibi destekleri ve yayılma operatörünü ...props
kullanan ek destekleri kabul eder. CustomSelect
bileşeni, select
öğesinin özelliklerinin kullanılmasına izin vererek ve ek destekleri kabul ederek, Liskov Değiştirme İlkesini (LSP) izler.
“Hiçbir kod kullanmadığı yöntemlere bağımlı olmaya zorlanmamalıdır.” — Vikipedi.
// ❌ 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> ); }
Yukarıdaki örnekte, ürün detaylarının tamamını, gerektirmese de ProductThumbnailURL
bileşenine aktarıyoruz. Bileşene gereksiz riskler ve karmaşıklık katar ve Arayüz Ayrıştırma İlkesini (ISP) ihlal eder.
// ✅ 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> ); };
Revize edilen kodda ProductThumbnailURL
bileşeni, ürün ayrıntılarının tamamı yerine yalnızca gerekli bilgileri alıyor. Gereksiz riskleri önler ve Arayüz Ayrıştırma Prensibini (ISP) destekler.
"Bir varlık somutlaştırmalara değil, soyutlamalara dayanmalıdır" - Vikipedi.
// ❌ 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> ); };
CustomForm
bileşeni alt öğelerine sıkı bir şekilde bağlı olduğundan esnekliği engeller ve davranışını değiştirmeyi veya genişletmeyi zorlaştırır.
// ✅ 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> ); };
Revize edilen kodda formun soyutlaması görevi gören AbstractForm
bileşenini tanıtıyoruz. onSubmit
işlevini bir destek olarak alır ve form gönderimini yönetir. Bu yaklaşım, üst düzey bileşeni değiştirmeden form davranışını kolayca değiştirmemize veya genişletmemize olanak tanır.
Meraklı kal; kodlamaya devam edin!
Referans:
da yayınlandı.