Context API 의존성 주입에 대한 고찰

LEE KAYOUNG / KATIE·2024년 9월 14일
0

1️⃣ React Context는 무엇인가?


https://react.dev/learn/passing-data-deeply-with-context

“Passing Data Deeply with Context”
Usually, you will pass information from a parent component to a child component via props. But passing props can become verbose and inconvenient if you have to pass them through many components in the middle, or if many components in your app need the same information. Context lets the parent component make some information available to any component in the tree below it—no matter how deep—without passing it explicitly through props.

리액트 공식문서에서는 Context를 props 깊게 전달해야될 때 함께 사용된다 라고 정의 내려져 있다. 컨텍스트는 부모 구성 요소가 그 아래의 전체 트리에 데이터를 제공하도록 한다. → 관리에 대한 내용이 전혀 없으며 값의 '전달', '공유'만을 언급한다.

나는 퍼넬 패턴을 고안할 때 Context API vs Recoil 이렇게 생각하며 → 즉 Context API == 상태 관리 라이브러리로 전역으로 상태를 관리하기 위해 Context API를 도입했는데, 애초에 이 둘은 비교선상이 알맞지 않았다.

📁 Props drilling이란,


출처 : https://react.dev/learn/passing-data-deeply-with-context

But passing props can become verbose and inconvenient when you need to pass some prop deeply through the tree, or if many components need the same prop. The nearest common ancestor could be far removed from the components that need data, and lifting state up that high can lead to a situation called “prop drilling”.

⓵ Props drilling은 React 애플리케이션에서 데이터를 전달하기 위해 필요한 과정을 설명하는 용어이다.

이는 컴포넌트 트리에서 데이터를 하위 컴포넌트로 전달하기 위해 **중간 컴포넌트를 통해 프로퍼티를 내려주는** 것을 의미한다. 이러한 중간 컴포넌트는 원하는 자식 컴포넌트에게 프로퍼티를 전달하기 위해 필요하지만 해당 값을 직접 사용하지 않는 경우에도 프로퍼티를 받고 전달해야 한다는 특징이 있다

⓶ Props drilling으로 발생하는 문제점
- 1. property 데이터 형식 변경의 불편함: Prop drilling 데이터의 데이터 형식을 변경해야 하는 경우, 컴포넌트 계층 전체에서 업데이트하는 것이 어려울 수 있습니다.
- 2. 중간 컴포넌트에 불필요한 property 전달: 컴포넌트 분리 과정에서 중간 컴포넌트를 통해 불필요한 프로퍼티가 전달될 수 있어 불필요한 복잡성을 초래할 수 있습니다.
- 3. 누락된 property 인지의 어려움: 필요한 프로퍼티가 타겟 컴포넌트에 전달되지 않은 상황을 인지하기 어려울 수 있어 잠재적인 문제를 발견하기 어려울 수 있습니다.

children

이러한 Props drilling의 특징으로 Context를 이용하면, 트리 단계마다 명시적으로 props를 넘겨주지 않아도 많은 컴포넌트가 값을 공유하도록 할 수 있다.

즉, Context API에 대하여 많은 개발자들이 useContext를 상태 관리를 위한 API로 오해하고 있다.
엄밀히 말하면 Context는 Props drilling 극복을 위한 방법 즉, 상태를 주입해 주는 API이다.

2️⃣ Context 사용법


⓵  React.createContext 메소드를 통하여 context 객체를 만들어 사용한다.

⓶ context 객체를 만든 후에 React.Provider 통하여 전달 받은 값을 하위에 위치하는 컴포넌트들에게 전달 해줄 수 있습니다. provider 안에 provider를 사용 할 수도 있고 context 객체는 가장 가까운 provider를 통하여 값을 전달 받는다.
→ 저는 이 특징을 사용 context를 해당 layout만 쓰이게 만드는 지역 전역변수로 사용할 수 있겠다 생각 했다.
⓷ 그리고 useFunnel 훅에서 이 상태를 관리할 수 있도록 했다.

이렇게 지역적 전역 변수로 사용하니 마치 전역으로 상태를 관리하는 것 같다.
즉, Context가 사용되는 상황은
1. 전역적 데이터를 다룰 때,
2. 일부 컴포넌트 트리에서 자주 공유되는 데이터를 다룰 때 이다.
하지만 Context는 상태 관리 툴이 아니라는 점을 얘기해보자.

3️⃣ Context API가 상태 관리 툴이 아닌 이유



위 사진과 같이 생각을 해왔는데
⓵ Context를 일반적인 상태 관리도구로써 사용한다면, Provider이 계속 중첩되어 쌓여갈 것이다. 아래와 같이 중첩으로 사용됐을 경우 값이 변경될 때 추적이 어려워진다.

<Context1.Provider value={...}>
    <Context2.Provider value={...}>
        <Context3.Provider value={...}>
            <Context4.Provider value={...}>
                ...
            </Context4.Provider>
        </Context3.Provider>
    </Context2.Provider>
</Context1.Provider>

⓶ 상태관리 툴의 조건 : 초기값 저장 현재값 읽기 값 업데이트

상태(State)란 어플리케이션의 작동에 관여하는 모든 데이터를 말한다.
중요한 것은 데이터가 필요 시 저장되고(stored), 읽히고(read), 업데이트되고(updated), 사용되어야(used) 한다는 점이다.

Context는 자체적으로 어딘가에 값을 저장하지도 않고 useState의 setState처럼 값을 변경하는 기능을 제공하지도 않는다!

→ 그저 Provider에 넣어준 value를 useContext를 통해 꺼내쓴다. 즉 Context는 아무것도 관리하지 않고 중간다리의 역할만 한다.
이러한 개념을 의존성 주입(Dependency Injection)이라 한다. 하위 컴포넌트에서 특정 값을 필요로 할 때, 직접 생성 또는 마련하지 않고 런타임 시에 상위 컴포넌트의 어딘가로부터 값을 내려받는 것이다.

4️⃣ Context 성능 주의점


context value 값 변경시 context provider 하위에 별다른 처리가 없다면 하위컴포넌트 모두 리렌더링된다!

context provider 컴포넌트는 여러개의 state 상태값을 가질 수 있고, value 값을 통하여 하위 컴포넌트 트리에 있는 컴포넌트들에게 값을 전달하여 준다.

React에서 리렌더링 조건 중 하나는 부모 컴포넌트가 리렌더링 된다면 자식 컴포넌트가 리렌더링이 된다 라는 것이 있다. 그렇다면 context provider가 리렌더링 된다면? 당연히 아무 처리도 하지 않은 자식 컴포넌트들은 무작정 리렌더링이 된다.

물론 하위 컴포넌트 트리가 간단하거나 복잡한 계산을 수행하지 않는다면 상관없지만 이외의 상황에서는 이를 주의해야한다.

이를 해결하는 방법은 2가지가 있다.

  1. Provider 바로 아래의 컴포넌트에 React.memo를 사용
  2. provider prop의 children으로 하위 컴포넌트를 전달

나는 1번을 사용하여 프로젝트에 도입하였다.

5️⃣ 결론


  • 단순히 prop-drilling을 피하고 싶다면 Context를 사용하라.
  • 컴포넌트 복잡도가 보통 수준이거나 외부 라이브러리를 사용하고 싶지 않다면 ContextuseReducer 조합을 사용하라. → redux 패턴을 흉내낼수 있다.
  • 상태 변화 추적이 필요할 때, 상태 변화 시 특정 컴포넌트만 리렌더하고 싶을 때, 사이드 이펙트를 강력하게 컨트롤하고 싶을 때는 Redux + React-Redux를 사용하라.
    혹은 외부 라이브러리를 선택하자.

useReducer: reducer 함수를 사용하여 보통 수준의 복잡도를 지닌 리액트 컴포넌트의 상태를 관리한다.

참고문헌


https://kentcdodds.com/blog/prop-drilling

https://blog.isquaredsoftware.com/2021/01/context-redux-differences/

https://velog.io/@rla0591/React-Context를-대충-알고-사용하는-사람들을-위한-주의글

https://lasbe.tistory.com/166#📌Context_Api는상태관리툴이아닌가?

profile
[궁금한 것들 이리저리..쿵]

0개의 댓글