“Một mô-đun phải chịu trách nhiệm cho một và chỉ một diễn viên.” —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> ); };
Trong ví dụ trên, thành phần Products
vi phạm Nguyên tắc Trách nhiệm Đơn lẻ bằng cách đảm nhận nhiều trách nhiệm. Nó quản lý việc lặp lại các sản phẩm và xử lý kết xuất giao diện người dùng cho từng sản phẩm. Điều này có thể làm cho thành phần khó hiểu, bảo trì và thử nghiệm trong tương lai.
// ✅ 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> ); };
“Các thực thể phần mềm (lớp, mô-đun, chức năng, v.v.) phải được mở để mở rộng, nhưng đóng để sửa đổi.” —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> ); }
Trong ví dụ trên, chúng tôi sửa đổi thành phần Button
hiện có bằng cách thêm một icon
chống đỡ. Việc thay đổi một thành phần hiện có để đáp ứng các yêu cầu mới vi phạm Nguyên tắc Đóng-Mở. Những thay đổi này làm cho thành phần này trở nên dễ vỡ hơn và có nguy cơ xảy ra các tác dụng phụ ngoài ý muốn khi được sử dụng trong các ngữ cảnh khác nhau.
// ✅ 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> ); }
Trong ví dụ trên, chúng tôi tạo một thành phần chức năng IconButton
riêng biệt. Thành phần IconButton
gói gọn việc hiển thị nút biểu tượng mà không sửa đổi thành phần Button
hiện có. Nó tuân thủ Nguyên tắc Mở-Đóng bằng cách mở rộng chức năng thông qua thành phần hơn là sửa đổi.
“Các đối tượng loại con nên được thay thế cho các đối tượng siêu loại” - 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> );
Trong ví dụ trên, chúng ta có một thành phần BadCustomSelect
nhằm phục vụ như một đầu vào lựa chọn tùy chỉnh trong React. Tuy nhiên, nó vi phạm Nguyên tắc thay thế Liskov (LSP) vì nó hạn chế hành vi của phần tử select
cơ sở.
// ✅ 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> ); };
Trong mã sửa đổi, chúng ta có một thành phần CustomSelect
nhằm mở rộng chức năng của thành phần select
tiêu chuẩn trong React. Thành phần chấp nhận các đạo cụ như value
, iconClassName
, handleChange
và các đạo cụ bổ sung bằng cách sử dụng toán tử trải rộng ...props
. Bằng cách cho phép sử dụng các đặc điểm của phần tử select
và chấp nhận các đạo cụ bổ sung, thành phần CustomSelect
tuân theo Nguyên tắc thay thế Liskov (LSP).
“Không có mã nào bị buộc phải phụ thuộc vào các phương thức mà nó không sử dụng.” —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 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> ); }
Trong ví dụ trên, chúng tôi chuyển toàn bộ chi tiết sản phẩm tới thành phần ProductThumbnailURL
, mặc dù thành phần này không yêu cầu. Nó bổ sung thêm rủi ro và độ phức tạp không cần thiết cho thành phần, đồng thời vi phạm Nguyên tắc phân tách giao diện (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> ); };
Trong mã sửa đổi, thành phần ProductThumbnailURL
chỉ nhận thông tin bắt buộc thay vì toàn bộ chi tiết sản phẩm. Nó ngăn ngừa những rủi ro không cần thiết và thúc đẩy Nguyên tắc Phân chia Giao diện (ISP).
“Một thực thể nên phụ thuộc vào sự trừu tượng, không phải sự cụ thể hóa” - 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> ); };
Thành phần CustomForm
được liên kết chặt chẽ với các phần tử con của nó, ngăn cản tính linh hoạt và khiến nó khó thay đổi hoặc mở rộng hành vi của nó.
// ✅ 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> ); };
Trong mã sửa đổi, chúng tôi giới thiệu thành phần AbstractForm
, hoạt động như một phần trừu tượng cho biểu mẫu. Nó nhận chức năng onSubmit
làm chỗ dựa và xử lý việc gửi biểu mẫu. Cách tiếp cận này cho phép chúng tôi dễ dàng trao đổi hoặc mở rộng hành vi của biểu mẫu mà không cần sửa đổi thành phần cấp cao hơn.
Hãy luôn tò mò; tiếp tục viết mã!
Thẩm quyền giải quyết:
Cũng được xuất bản .