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

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

经过 Arul Valan Anto
Arul Valan Anto HackerNoon profile picture

Arul Valan Anto

@arulvalananto

Hey! I am a Software Engineer who has crafting robust...

11 分钟 read2023/07/07
Read on Terminal Reader
Read this story in a terminal
Print this story

太長; 讀書

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

Arul Valan Anto

@arulvalananto

Hey! I am a Software Engineer who has crafting robust and scalable web applications for years.

0-item

STORY’S CREDIBILITY

Code License

Code License

The code in this story is for educational purposes. The readers are solely responsible for whatever they build with it.

在这篇博客中,我将演示 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 项目中应用这些原则。


保持好奇心;继续编码!


参考:


也发布在这里


L O A D I N G
. . . comments & more!

About Author

Arul Valan Anto HackerNoon profile picture
Arul Valan Anto@arulvalananto
Hey! I am a Software Engineer who has crafting robust and scalable web applications for years.

標籤

这篇文章刊登在...

Read on Terminal Reader
Read this story in a terminal
 Terminal
Read this story w/o Javascript
Read this story w/o Javascript
 Lite
X REMOVE AD