DIP(Dependency Inversion Principle : 의존 역전 원칙

YI·2022년 1월 14일
10

SOPT 29기 앱잼에서, 대학생을 위한 사이드 프로젝트 팀빌딩 플랫폼 서비스를 제작하고 있다. 개발을 하던 중, React의 합성과 상속에 대한 이해가 필요한 부분이 있어 관련된 내용을 정리했다.


React에서 구조가 A -> B -> C 이런식으로 이루어진 컴포넌트가 있다고 가정해보자. 이런 구조인 경우, 보통은 A가 C에 prop을 전달하기 위해, B로 드릴링 된다라고 생각한다.

Prop Drilling

먼저 prop drilling이 무엇인지 알아보자.

Prop Drilling? prop drilling은 props를 오로지 하위 컴포넌트에 전달하는 용도로만 사용되는 컴포넌트를 거치면서 React 컴포넌트 트리의 한 부분에서 다른 부분으로 데이터를 전달하는 과정을 의미한다.

예를 들어,아래와 같이 content를 실제 사용하는 하위 컴포넌트에 전달하기 위해,App -> First -> Second -> ComponentNeedingProps 로 하나씩 내려가는 것을 prop drilling이라고 한다.

export default function App() {
  return (
    <div className="App">
      <FirstComponent content="Who needs me?" />
    </div>
  );
}

function FirstComponent({ content }) {
  return (
    <div>
      <h3>I am the first component</h3>;
      <SecondComponent content={content} />|
    </div>
  );
}

function SecondComponent({ content }) {
  return (
    <div>
      <h3>I am the second component</h3>;
      <ThirdComponent content={content} />
    </div>
  );
}

function ComponentNeedingProps({ content }) {
  return <h3>{content}</h3>;
}

사실 Prop Drilling 자체가 문제가 되는 것은 아니다. prop 전달이 3~5개 정도의 컴포넌트를 거쳐 내려가는 정도는 상관없다. 하지만, props의 전달이 과도하게 많아지면, 코드를 읽을 때, 해당 props를 추적하기 매우 까다로워지며, 이는 곧 유지 보수를 어렵게 하는 요인이 된다.

그렇다면, 과도한 Drilling을 피하는 방법은 없을까?

  1. 전역 상태관리 라이브러리를 사용하여 해당 값이 필요한 컴포넌트에서 직접 Prop을 불러 사용할 수 있다.

  2. children 을 활용한다.

children이 뭔지 보고 넘어가자!

간단하게 children을 어떻게 사용하는지 보자.

ApprovalCardPerson 컴포넌트를 감싸고 있다. 렇게 작성할 경우, <ApprovalCard> 컴포넌트의 props로 <Person /> 컴포넌트가 전달된다.

<div className="Children">
        <h1>React Children</h1>
        <hr />
        <section className="card-section">
          <ApprovalCard>
            <Person
              name="joonsikyang"
              email="joonsik@wecode.com"
              position="Frontend Mentor"
            />
          </ApprovalCard>
	</section>
</div>

아래와 같은 형태로,<ApprovalCard> 컴포넌트의 props로 <Person /> 컴포넌트가 전달되는 것이다.

props = {children : <Person />};

ApprovalCard는 props로 받은 Person 컴포넌트를 내부에서 사용할 때, this.props.childern 으로 접근할 수 있다.

children 값으로 컴포넌트 뿐 아니라, plain text, multiple element 등 어떤 것도 전달할 수 있다. 또 이를 응용하여 같은 레이아웃안에 다른 내용을 보여주고 싶은 경우, children을 사용하면 효율적으로 컴포넌트를 재사용할 수 있다.

컴포넌트 재사용성을 높이며, 컴포넌트를 가장 효율적으로 만드는 방법으로 children을 상황에 맞게 적절히 사용해 보자!


DIP(Dependency Inversion Principle : 의존 역전 원칙

자, 이제 다시 처음으로 돌아가, 과도한 Props Drilling을 어떻게 피할 수 있는지 알아보자.

구조가 A -> B -> C 이런식으로 이루어진 컴포넌트에서는, A의 props이 C로 전달되기 위해, B로 Prop Drilling이 발생한다.

하지만, 이러한 구조를,

B가 props로 C를 받고, C는 A에 선언되어, 관계를 A -> C -> B로 역전시킬수 있다.

이런식으로 구조를 역전시키면, 여전히 DOM에서는 A -> B -> C 순으로 렌더링 되지만, 코드 상에서는 A -> C -> B 관계가 되는 것이다. 이렇게 컴포넌트의 관계를 역전시키면, 기존의 구조에서는 Drilling이 발생한 B 컴포넌트는, AC컴포넌트간 props 이동에는 전혀 관여하지 않게 된다.

조금 더 자세한 예시를 살펴보자.
A 컴포넌트 , B 컴포넌트, C 컴포넌트가 있다고 할때,
B 컴포넌트는 단순히 페이지의 레이아웃만을 잡아주는 컴포넌트라 따로 필요한 data가 없다. 하지만 C 컴포넌트는 A가 주는 데이터가 필요한 상황이라고 하자,

이때, 바로 A에서 B에게 C가 필요한 데이터를 내려주고, B도 똑같은 데이터를 그저 아래로 전달만 해주면 원하는 대로 잘 동작하지만, Props Drilling이 발생하는 단점이 있다.

원하는대로 모든 것이 동작하고, Props Drilling 도 발생하지 않게 이를 수정하는 방법이 있다.

🎊해결방법 : A에서 B와 C 컴포넌트를 모두 만들어준다. 그리고, B컴포넌트에 C를 props로 전달한다. 전형적으로 children을 사용하는 방법이다!

즉, 이러한 방식으로 하면, C는 필요한 A의 데이터를 다 가지고 있게 되고, B는 그저 필요한 데이터가 가득 차 있는 C를 그저 렌더링만 해주면 된다.
B의 입장에선, props = {children : c;} 로 C 컴포넌트를 children으로 받게 되는 것 이다.

이를 DIP(Dependency Inversion Principle : 의존 역전 원칙 이라고 한다.

마지막으로 실제 코드를 보며, 실제로 어떻게 쓰이는 지 알아보자.

/** 
* A : BrowseLiveChannels (실제 데이터를 가지고 있는 A 컴포넌트)
* B : BrowseLiveChannelsLayout (단순히 C 컴포넌트를 렌더링해주는 B 컴포넌트)
* C : CategoryTab (A 컴포넌트의 데이터를 실제 사용하는, C 컴포넌트)
**/

... 
import { BroadcastCard } from "./BroadcastCard";
import { CategoryTab, CategoryTabItem } from "./CategoryTab";
import { BrowseLiveChannelsLayout } from "./Layout";

... 

export function BrowseLiveChannels() {
  	const [selectedTab, setSelectedTab] = useState("game");
  		// .. 생략
  
	return (
          <BrowseLiveChannelsLayout
            tab={
              <CategoryTab value={selectedTab} onChange={(e, val) => setSelectedTab(val)}>
                {tabTypes.map((tab) => (
                  <CategoryTabItem label={tab.label} value={tab.key} key={tab.key} />
                ))}
              </CategoryTab>
            }
            cards={cards}
            mobileCards={mobileCards}
          />
  );
}

Reference

https://github.com/we-sopt-together-twitch/frontend/blob/main/src/components/browseLiveChannels/BrowseLiveChannels.tsx

https://ko.reactjs.org/docs/composition-vs-inheritance.html

profile
Junior Frontend Developer

3개의 댓글

comment-user-thumbnail
2022년 1월 21일

YI 님 개쩔어염

답글 달기
comment-user-thumbnail
2022년 9월 15일

친절한 예시 덕분에 DIP 이해하는데 도움을 받았습니다!
감사합니다!

답글 달기
comment-user-thumbnail
2024년 10월 29일

잘 보았습니다.

답글 달기

관련 채용 정보