React Learn - Props 를 깊은 곳 까지 넘기자 , UseContext

ChoiYongHyeun·2024년 2월 25일
0

리액트

목록 보기
8/31
post-thumbnail

이번 공부 내용은 리액트 공식문서인 Passing Data Deeply with Context 를 중점으로 하여 공부해보았다.


컴포넌트를 구성하다보면

상위 컴포넌트에서 깊은 하위에 존재하는 컴포넌트에게 props 를 전달해주기 위해

하위 컴포넌트를 감싸고 있는 중단 컴포넌트에게 props 를 건내줘야 하는 경우가 발생하곤 한다.

이전에 만들었던 투 두 리스트 만들기 토이프로젝트에서의 예시가 딱 그렇다.

ToDoList 컴포넌트는 tasks 라는 props 를 받도록 하는데

ToDoList 컴포넌트는 tasks 라는 props 를 받아 사용하기 보다

하위 컴포넌트인 ToDoInput 이나 ToDoText 컴포넌트에게 props 를 전달만 해주기 위해

tasks props 를 인수로 받아야 한다.

위 예시는 컴포넌트의 깊이가 얕기 때문에 크게 문제 삼아지지는 않을 수 있지만

전달 받고자 하는 컴포넌트가 하단에 위치 할 수록 문제는 더욱 커진다.

Props Drilling

LeafComponent 에게 PassingProps 를 건내주기 위해서 First , Second , Thrid Component 에서

PassingProps 를 받고 있는 모습을 볼 수 있다.

본인에게 필요하지도 않은 props 를 하위 컴포넌트에 전달하기 위해서 말이다.

이러한 경우를 Props Drilling 이라고 한다 .


Props Drilling 의 단점

Props Drilling 이 있을 경우의 문제는 무엇일까 ?

1. BoilerPlate 코드 증가

보일러 플레이트 코드란 어떤 단순한 작동을 위해 불필요한 코드가 반복적으로 나탄는 것을 의미한다.

위 예시에서는 LeafComponent 에게 props 를 전달하기 위해

위의 모든 컴포넌트에게 PassingProps 를 인수로 받도록 하여 전달하는 반복적인 코드 양상이 나타난다.

이는 코드를 장황하게 만들고 가독성을 떨어뜨릴 수 있다.

2. 유지보수 어려움

컴포넌트의 트리 구조를 변경 할 때 마다 Drilling 되는 props 들을 관리하기 위해 신경써야 한다.

이는 유지보수를 어렵게 하고 코드베이스를 변경 할 때 더 많은 작업을 필요로 한다.

3. 성능 저하

리액트는 re-rendering 해야 할 다양한 조건 중 하나인 Props 가 변경 됐을 때 해당 Props 를 인수로 받는

모든 컴포넌트를 리렌더링 한다.

이는 위 예시에서 LeafComPonent 에게 전달할 props 를 변경만 하더라도, FirstComponent 부터 LeafComponent 까지의 모든 컴포넌트가 리렌더링 된다는 것을 의미하며 이는 성능을 저하시킨다.

4. 컴포넌트 간 결합도 증가

컴포넌트간 결합도가 증가한다는 것은 컴포넌트간 서로 의존적이게 되어 강하게 결합된 상태를 의미한다.

LeafComponent 까지 향하는 FirstComponent ... 이하의 컴포넌트들은 서로 PassingProps 를 전달하기 위해

강하게 결합되어 있다.

이는 연결되어 있는 컴포넌트간 의존성으로 인해 특정 컴포넌트만을 추출하여 다른 컴포넌트에서 사용하거나

특정 컴포넌트 하나만 수정하는 것이 필연적으로 힘들어진다는 의미이다.


PropsDrilling 을 해결하기 위해 리액트에서 제공하는 Hook

이런 Props Drilling 과 같은 문제를 해결하기 위해 리액트에서는 Context 라는 개념을 가진 Hook 을 제공한다.

먼저 예시를 통해 알아보자

CreateContext

react.CreateContext 를 이용해 Context 를 생성해준다.

생성된 Context 객체는 컴포넌트들이 전역적으로 데이터를 공유 할 수 있게 해주는 메커니즘이다.

LeafComponent 에서 전달받고자 하는 props 를 전역적으로 공유 할 수 있게 해주었으니 모든 컴포넌트에서

passingProps 부분을 모두 제거해주자

이후 rootComponent 에 해당하는 FirstComponent 를 이전에 생성한 LeafContext.Provider 컴포넌트로 감싸주자

이렇게 감싸주면 FirstComponent 하위에 존재하는 모든 컴포넌트들은 LeafContext 에서 정의된 value 값을 사용 할 수 있게 된다.

createContext(defaultValue)

createContext 에서 쓰이는 맨 처음 defaultValue 값은 Context.Provider 를 이용 할 때

value 값이 지정되지 않았을 때 사용되는 디폴트 값이다.

만약 사용시 value 값을 지정해준다면 defaultValue 값이 아닌 지정한 value 값이 사용된다.

useContext

useContext 는 해당 컴포넌트의 상위 컴포넌트에서 정의된 Context.Providervalue 값을 가져온다.

위에서는 App 컴포넌트에서 정의된 LeafContext.Provider 가 제공하는 value 값을 가져와 사용 할 수 있게 된다.

useContext 는 항상 가장 가까운 상위 컴포넌트의 컨텍스트를 이용한다.

위 예시처럼 LeafComponent 에게 전달하기 위한 Context

Seconde , Thrid , Leaf Component 모두에서 정의되었을 때

LeafComponent 는 자신의 가장 가까운 상위 컴포넌트에서 정의된 context 를 이용한다.

가장 가까운 상위 컴포넌트의 컨텍스트만 이용하는 이유

생각하기 앞서 Context 가 왜 필요한지에 대한 회고가 필요하다.

Context 를 이용하면 우선 Props Drilling 을 방지 하여 불필요한 re-rendering을 막고 컴포넌트 간 의존성을 내린다는 장점이 있다.

컴포넌트간 의존성이 낮을 수록 컴포넌트를 재사용 하기도, 수정하기도 용이하기 때문에

가장 가까운 상위 컴포넌트의 컨텍스트만 이용하도록 하여

최대한 컴포넌트 간 의존성의 깊이를 낮추도록 한다.

만일 다음처럼 FirstComponentThridComponent 모두 LeafContext.Provider 를 통해 Context 를 제공하고

본인이 가지고 있는 state 값에 따라 전달하고자 하는 Context 의 값이 변경된다고 해보자

앞서 설명했듯이 LeafComponent 는 가장 가까운 상위 컴포넌트의 Context 를 이용하기 때문에

FirstComponentContext 변화에는 반응하지 않는 모습을 볼 수 있다.

이를 통해 LeafComponentFirstComponent 가 아닌 가장 가까운 부모 컴포넌트인 ThridComponent 상태에만

의존하기 때문에

결합도의 깊이가 낮아진 모습을 볼 수 있다.


Context 를 너무 남용하지는 마세요

위 게시글만 보면

ㅋㅋ 아 ~ stateprops 로 뭐하로 넘겨주냐고 ~ context 이용하면 되는데 ~ ㅋㅋ

라는 생각이 든다.

하지만 contextProps Drilling 이 발생 할 때 사용할 수 있는 대안책 역할이지

Props Drilling 을 사용해도 좋다는 것은 아니다.

Context 를 남용 할 때 생기는 단점

1. Props 를 건내주는 흐름을 추적하기가 어렵다.

Context 를 이용하면 value 값으로 넘겨줄 Props 값이 전역에서 접근 가능하기 때문에

state 의 변화에 따른 컴포넌트의 변화를 추적하기 어렵다는 단점이 있다.

기존 Props 를 건내주는 방식은 각 계층별로 하위 컴포넌트에게 폭포처럼 하위로 전달해주기 때문에

컴포넌트의 계층적 구조를 통해 흐름을 이해 하기 편하지만

Context 의 경우는 계층적 구조를 무시하고 전달하기 때문에 건내주는 흐름을 추적하기가 어렵다.

2. Context 를 구독하고 있는 컴포넌트가 많을 수록 가독성이 떨어진다.

만일 하위 컴포넌트에게 props 를 전달해줄 때 직접 전달하는 것이 아니라

모두 Context 를 이용해서 전달하려고 모두 Context 를 이용해서 전달한다면

불필요한 코드량이 늘어날 뿐 아니라 상위 Context 를 확인하기 위해 여기 저기 컴포넌트들을 확인해야 하는 일이 발생한다.

Context 를 사용하기 전 시도해 볼 수 있는 방법들

Context 를 이용하는 이유는 Props Drilling 을 최대한 대안적으로 해결하기 위함이였는데

가장 좋은 방법은 Props Drilling 을 지양하는 것이 가장 중요하다.

1. 만약 Props Drilling 이 발생 할 것 같다면 해당 데이터를 상위 컴포넌트에서 정의하지 말자

하위 컴포넌트에게 건내주고자 하는 props 가 매우 높은 위치의 부모 컴포넌트로부터 전달 받아야 한다면

데이터를 하위 컴포넌트에서 생성하고 관리하도록 이용하든지

해당 데이터를 이용한 로직을 최대한 지양해야 한다.

상위 컴포넌트로부터 Drilling 되어 내려오는 데이터는 모든 컴포넌트의 의존성을 높혀

컴포넌트의 관리성을 매우 낮춘다.

2. 만약 Props Drilling 이 필연적으로 발생 해야 한다면 children props 를 잘 이용해보자

이전 Props Drilling 에서 passingProps 를 계속하여 Drilling 하여 보내게 됐던 가장 큰 이유는

최상단 컴포넌트에서 FirstComponent 를 하나만 호출하도록 설계되었기 때문이다.

한 눈에 어떤 컴포넌트에게 props 를 건내주고 있는지를 한 눈에 알 수 있다면

Props Drilling 으로 인해 발생하는 문제를 해결 할 수 있을 것이다.

각 컴포넌트들이 children 을 가질 수 있도록 하게 하여 최상단 컴포넌트인 App 컴포넌트에서

props 를 건내준다면 훨씬 컴포넌트의 계층적 구조를 이해하고

컴포넌트간 어떤 props 를 받고 있는지 한 눈에 이해하기 쉬워질 것이다.


ContextUse Case

위 예시들을 보면 또

ㅋㅋ 아 ~ Context 사용 안하는게 훨씬 낫네 ~ Context 왜 씀 ?

이런 편협한 생각이 들곤 한다.

내가 그랬다.

그럼에도 불구하고 useContext 가 유용하게 사용되는 몇 가지가 존재한다.

특히 모든 컴포넌트가 일괄적으로 동일한 state 를 받아야 한다면, 모두에게 props 로 건내주기 보다 Context 로 정의해두고 컴포넌트 내에서 해당 Context를 호출하는 것이 훨씬 효율적이다.

1. Theming

테마의 일부인 다크모드의 경우 UX 를 높혀주는 방법 중 하나로 사용자가 특정 버튼을 누르면

모든 컴포넌트들이 해당 테마에 맞춰 리렌더링 된다.

페이지의 테마를 전역에서 Context 로 정의해두고 각 컴포넌트 내에서 호출하여 사용한다면 훨씬 효과적일 것이다.

2. Current Account

페이지에서 클라이언트가 로그인을 하고 있는지, 하고 있지 않은지에 따라 렌더링 하는 영역이 다를 것이다.

내용도 다를 것이고 말이다.

이에 페이지의 현재 상태를 나타내는 것 또한 전역적인 Context 로 정의해두고

컴포넌트 내부에서 받아 호출한다면 효과적일 것이다.

3. Routing

아직 나는 리액트 라우터에 대한 내용을 배우지 않았다.

이 부분은 리액트 공식문서의 내용을 추가하도록 하겠다.

Routing: Most routing solutions use context internally to hold the current route.
This is how every link “knows” whether it’s active or not. 
If you build your own router, you might want to do it too.

4. Managing State

어플리케이션이 커지면 커질 수록 , 상단에 있는 state 를 하위 컴포넌트에서 수정하는 경우가 잦아질 것이다.

이런 경우에도 useContext 를 이용하면 효과적으로 상태를 변경할 수 있다.

이는 다음 챕터에서 자세히 공부해보도록 하자


Recap

  • Context 는 컴포넌트들이 전역적으로 관리되는 데이터들을 받을 수 있도록 한다. (상위 컴포넌트에서 제공하는)
  • Contextpassing 하기 위해서는
    1. createContext 를 이용해 passingContext 를 생성한다.
    2. Context 를 이용할 컴포넌트 내에서 useContext 로 상단에서 정의된 Context 를 받아온다.
    3. Context 를 제공할 컴포넌트는 children<Context.Provider value = {}> 등으로 감싸준다.
  • Context 는 컴포넌트의 위치에 상관 없이 어디서나 Provider 형식으로 Context 를 제공 할 수 있다.
  • Context 를 이용하면 컴포넌트의 최상위 컴포넌트에게만 props를 전달받는 딱딱한 계층적 구조에서 탈피하여 주위에 있는 컴포넌트로부터 props 를 받을 수 있도록 하게 도와준다.
  • 하지만 Context 를 이용하기 전 props 를 전달하는 방식이 없는지 먼저 생각해보자 . props 를 이용하도록 하면 컴포넌트의 계층적 구조를 한 눈에 이해하기 쉬워질 것이다.
profile
빨리 가는 유일한 방법은 제대로 가는 것이다

0개의 댓글