paint-brush
只需 8 分钟即可掌握扎实的原则!经过@arulvalananto
26,411 讀數
26,411 讀數

只需 8 分钟即可掌握扎实的原则!

经过 Arul Valan Anto11m2023/07/07
Read on Terminal Reader

太長; 讀書

在这篇博客中,我将演示 SOLID 原则在 React 应用程序中的实现。读完本文后,您将完全掌握 SOLID 原则。
featured image - 只需 8 分钟即可掌握扎实的原则!
Arul Valan Anto HackerNoon profile picture
0-item

在这篇博客中,我将演示 SOLID 原则在 React 应用程序中的实现。读完本文后,您将完全掌握 SOLID 原则。在开始之前,让我向您简要介绍一下这些原则。

什么是 SOLID 原则?

SOLID 原则是五个设计原则,帮助我们保持应用程序的可重用性、可维护性、可扩展性和松散耦合性。 SOLID 原则是:


  • 单一职责原则
  • 开闭原则
  • 里氏替换原则
  • 接口隔离原则
  • 依赖倒置原则


好的,让我们分别研究一下这些原则。我以 React 为例,但其核心概念与其他编程语言和框架类似。

单一职责原则

“一个模块应该对一个且仅一个参与者负责。” ——维基百科。


单一职责原则规定组件应该有一个明确的目的或职责。它应该专注于特定的功能或行为,并避免承担不相关的任务。遵循 SRP 使组件更加集中、模块化并且易于理解和修改。我们来看看实际的实现。


 // ❌ 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> ); };


在上面的示例中, Products组件承担多重职责,违反了单一职责原则。它管理产品的迭代并处理每个产品的 UI 渲染。这可能会使组件在未来难以理解、维护和测试。

相反,这样做是为了遵守 SRP:

 // ✅ 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> ); };


这种分离确保每个组件都有单一的职责,使它们更易于理解、测试和维护。

开闭原则

“软件实体(类、模块、函数等)应该对扩展开放,但对修改关闭。” ——维基百科。


开闭原则强调组件应该对扩展开放(可以添加新的行为或功能),但对修改封闭(现有代码应保持不变)。这一原则鼓励创建能够适应变化、模块化且易于维护的代码。我们来看看实际的实现。


 // ❌ 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> ); }


在上面的示例中,我们通过添加icon属性来修改现有的Button组件。改变现有组件以适应新需求违反了开闭原则。这些更改使组件更加脆弱,并且在不同环境中使用时会带来意外副作用的风险。

相反,这样做:

 // ✅ 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> ); }


在上面的示例中,我们创建了一个单独的IconButton功能组件。 IconButton组件封装了图标按钮的渲染,而无需修改现有的Button组件。它遵循开闭原则,通过组合而不是修改来扩展功能。

里氏替换原则

“子类型对象应该可以替代父类型对象”——维基百科。


里氏替换原则 (LSP) 是面向对象编程的基本原则,强调层次结构中对象可替换性的需要。在 React 组件的上下文中,LSP 提倡这样的想法:派生组件应该能够替换其基础组件,而不影响应用程序的正确性或行为。我们来看看实际的实现。


 // ⚠️ 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> );


在上面的示例中,我们有一个BadCustomSelect组件,旨在充当 React 中的自定义选择输入。然而,它违反了里氏替换原则(LSP),因为它限制了基本select元素的行为。

相反,这样做:

 // ✅ 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> ); };


在修订后的代码中,我们有一个CustomSelect组件,旨在扩展 React 中标准select元素的功能。该组件接受诸如valueiconClassNamehandleChange之类的 props 以及使用展开运算符...props其他 props。通过允许使用select元素的特征并接受附加属性, CustomSelect组件遵循里氏替换原则 (LSP)。

接口隔离原则

“任何代码都不应该被迫依赖于它不使用的方法。” ——维基百科。


接口隔离原则(ISP)建议接口应该集中并根据特定的客户需求进行定制,而不是过于宽泛并迫使客户实现不必要的功能。我们来看看实际的实现。


 // ❌ 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> ); }


在上面的示例中,我们将整个产品详细信息传递给ProductThumbnailURL组件,即使它不需要它。它给组件增加了不必要的风险和复杂性,并且违反了接口隔离原则(ISP)。

让我们重构以遵守 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> ); };


在修改后的代码中, ProductThumbnailURL组件仅接收所需的信息,而不是整个产品详细信息。它可以防止不必要的风险并促进接口隔离原则 (ISP)。

依赖倒置原则

“一个实体应该依赖于抽象,而不是具体”——维基百科。


依赖倒置原则(DIP)强调高层组件不应该依赖于低层组件。这一原则促进了松散耦合和模块化,并有助于更轻松地维护软件系统。我们来看看实际的实现。


 // ❌ 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组件与其子组件紧密耦合,从而阻碍了灵活性,并使更改或扩展其行为变得困难。

相反,这样做:

 // ✅ 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> ); };


在修改后的代码中,我们引入了AbstractForm组件,它充当表单的抽象。它接收onSubmit函数作为 prop 并处理表单提交。这种方法使我们能够轻松地交换或扩展表单行为,而无需修改更高级别的组件。

结论

SOLID 原则提供了指导原则,使开发人员能够创建设计良好、可维护且可扩展的软件解决方案。通过遵循这些原则,开发人员可以实现模块化、代码可重用性、灵活性并降低代码复杂性。


我希望这篇博客能够提供有价值的见解,并启发您在现有或后续的 React 项目中应用这些原则。


保持好奇心;继续编码!


参考:


也发布在这里