paint-brush
단 8분 만에 손등처럼 견고한 원리를 마스터하세요!~에 의해@arulvalananto
25,246 판독값
25,246 판독값

단 8분 만에 손등처럼 견고한 원리를 마스터하세요!

~에 의해 Arul Valan Anto11m2023/07/07
Read on Terminal Reader
Read this story w/o Javascript

너무 오래; 읽다

이 블로그에서는 React 애플리케이션에서 SOLID 원칙을 구현하는 방법을 보여 드리겠습니다. 이 기사를 마치면 SOLID 원칙을 완전히 이해하게 될 것입니다.
featured image - 단 8분 만에 손등처럼 견고한 원리를 마스터하세요!
Arul Valan Anto HackerNoon profile picture
0-item

이 블로그에서는 React 애플리케이션에서 SOLID 원칙을 구현하는 방법을 보여 드리겠습니다. 이 기사를 마치면 SOLID 원칙을 완전히 이해하게 될 것입니다. 시작하기 전에 이러한 원칙에 대해 간략하게 소개하겠습니다.

SOLID 원칙이란 무엇입니까?

SOLID 원칙은 애플리케이션을 재사용, 유지 관리, 확장 가능하고 느슨하게 결합하는 데 도움이 되는 5가지 설계 원칙입니다. SOLID 원칙은 다음과 같습니다.


  • 단일 책임 원칙
  • 개방-폐쇄 원칙
  • Liskov 대체 원리
  • 인터페이스 분리 원리
  • 종속성 반전 원리


좋습니다. 각 원칙을 개별적으로 살펴보겠습니다. 저는 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 prop을 추가하여 기존 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 구성 요소를 수정하지 않고 아이콘 버튼 렌더링을 캡슐화합니다. 수정이 아닌 구성을 통해 기능을 확장하여 Open-Closed 원칙을 고수합니다.


Liskov 대체 원칙

"하위 유형 객체는 상위 유형 객체를 대체할 수 있어야 합니다." - Wikipedia


LSP(Liskov 대체 원칙)는 계층 구조 내 개체의 대체 가능성을 강조하는 개체 지향 프로그래밍의 기본 원칙입니다. 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> );


위의 예에는 React에서 사용자 정의 선택 입력 역할을 하는 BadCustomSelect 구성 요소가 있습니다. 그러나 기본 select 요소의 동작을 제한하므로 Liskov 대체 원칙(LSP)을 위반합니다.


대신 다음을 수행하십시오.

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


수정된 코드에는 React의 표준 select 요소의 기능을 확장하기 위한 CustomSelect 구성 요소가 있습니다. 구성 요소는 value , iconClassName , handleChange 와 같은 prop과 스프레드 연산자 ...props 사용하는 추가 prop을 허용합니다. select 요소의 특성을 사용하고 추가 소품을 허용함으로써 CustomSelect 구성 요소는 Liskov 대체 원칙(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 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> ); }


위의 예에서는 필요하지 않더라도 전체 제품 세부 정보를 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 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> ); };


수정된 코드에서 ProductThumbnailURL 구성 요소는 전체 제품 세부 정보 대신 필요한 정보만 받습니다. 불필요한 위험을 방지하고 인터페이스 분리 원칙(ISP)을 촉진합니다.

종속성 반전 원리

“하나의 실체는 구체화가 아닌 추상화에 의존해야 합니다” — Wikipedia.


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 프로젝트에 이러한 원칙을 적용하도록 영감을 주었기를 바랍니다.


호기심을 유지하세요. 계속 코딩하세요!


참조:


여기에도 게시되었습니다.