フックマスターになりたい (2): ContextAPI + useContext!

9rganizedChaos·2021년 9월 25일
0
post-thumbnail

리액트에서 데이터는 state와 props로 관리되며, topdown 형태로 데이터를 전달한다는 점은 리액트를 공부하는 사람들이라면 익히 알고 있는 사실이다. 이러한 리액트의 단방향 데이터 흐름 때문에 컴포넌트의 깊이가 깊어지면 props drilling이라는 문제가 생기고, Redux나 MobX, recoil 등이 이러한 문제를 개선하기 위해 전역에서 상태를 관래해주는 라이브러리들이다. 나와 같은 초보 개발자들 중 일부는 React만으로도 전역에서 상태를 관리해줄 수 있다는 사실을 모르고 있는 경우가 많다!

결론을 먼저 말하자면, 우리는 React의 ContextAPI를 이용해 컴포넌트 트리를 넘어 데이터를 공유할 수 있다! 특히 애플리케이션 전체에서 공유되는 데이터들, 예를 들어, UI테마, 선호 로케일, 현재 로그인한 유저 등 전역적으로 관리되어야 하는 데이터들의 경우 ContextAPI를 사용하면 좋다. 특히 이 API는 리액트 자체에 내장된 것으로 따로 라이브러리를 설치하지 않아도 된다는 장점이 있다. 작은 규모의 프로젝트라면 Context API를 통해 간편하게 전역 상태 관리를 하면 좋을 것이다!

without ContextAPI 😩

1) App, 2) ButtonBox, 3) ThemedButton 총 세 개의 컴포넌트로 구성된 App이 있다고 하자. 우리가 만약 App에서 ThemedButton으로 theme을 전달한다고 하면, 기존의 방식에서는 늘 ButtonBox를 거쳐야했다. 아래 예시와 같이!

with ContextAPI 🥳

그러나 ContextAPI를 활용하면 중간 엘리먼트인 ButtonBox에 props를 넘겨주지 않아도 된다!

여기까지가 ContextAPI의 아주 단순한 예시를 통해 알아본 전체적인 컨셉이다. 그렇다면 우선 각 API들의 역할을 좀 더 살펴보도록 하자.

APIs 📑

React.createContext 🔨

컨텍스트 객체를 만들 때 사용된다. 기본적으로 React는 트리 상위에서 가장 가까이에 있는 Provider를 탐색하고 감지될 경우 해당 Provider로부터 값을 읽어온다.

const MyContext = React.createContext(defaultValue);

createContext는 위와 같이 사용하는데, 매개변수 defaultValue는 트리 상위에서 적절한 Provider를 탐색해내지 못했을 때 쓰이는 값이다.

가시 말해 Provider가 감싸고 있지 않은 컴포넌트들의 경우 defaultValue를 가져오게 된다는 점!

  <ThemeContext.Provider value={this.state.theme}>
    <ThemedButton changeTheme={this.toggleTheme}/>
  </ThemeContext.Provider>
  <ThemedButton />

위와 같은 구조가 있을 때, Provider로 감싸진 ThemedButton은 context를 잘 참조해오지만, 바깥에 있는 ThemedButton은 defaultValue를 가져온다는 것이다.

Context.Provider 💁🏻‍♀️

context를 구독하는 컴포넌트 들에게 context의 변화를 알린다.

<MyContext.Provider value={/* 어떤 값 */}>

기본적으로 Provider는 Context 객체에 포함된 React 컴포넌트이고 value prop을 받아서 하위 컴포넌트에게 전달한다. Provider 하위에서 context를 구독하는 모든 컴포넌트는 Provider의 props가 바뀔 떄 재랜더링 된다!

Class.contextType 🎛

컴포넌트가 구독할 context를 지정해주는 역할을 한다. 이 API를 이용하면 하나의 context만 구독할 수 있다는 점에 주의해야 한다.

Context.Consumer 🛒

Provider가 context의 변화를 알려주는 역할을 한다면, Consumer는 변화를 구독하고 감지해오는 역할을 한다. 이 컴포넌트를 사용하면 함수 컴포넌트 안에서 context를 구독할 수 있다!

<MyContext.Consumer>
  {value => /* context 값을 이용한 렌더링 */}
</MyContext.Consumer>

Consumer 내부의 value는 상위 Provider 중 가장 가까운 Provider의 value prop과 같다. 상위에 적절한 Provider가 없으면 위에서 언급한 defaultValue를 가져온다.

Context.displayName 👲🏻

딱히 중요한 API는 아니다. 리액트 개발자 도구에서 출력될 Context 객체의 이름을 정할 수 있다.

const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';

<MyContext.Provider> // "MyDisplayName.Provider" in DevTools
<MyContext.Consumer> // "MyDisplayName.Consumer" in DevTools

ContextAPI 활용하기 (feat. useContext) 🔦

  • ContextAPI를 활용해 context의 밸류를 변경하기
  • 여러가지 context를 구독하기

아래는 CodeSandBox를 통해 구현한 간단한 ContextAPI 활용사례이다.

  • useContext는 함수 컴포넌트에서 Consumer 컴포넌트의 기능을 한다고 생각하면 편하다. 간단하게 컨텍스트에 접근할 수 있도록 도와주는 것! 다른 훅들이 그러하듯 컴포넌트 안에서 값을 바로 꺼내서 쓰는 것이다.

그러나 하나 주의할 점은 ContextAPI는 literally 맥락, 컨텍스트에 의존적이다. 어떤 프로바이드 컴포넌트 안에 있느냐에 따라 렌더링 된 모습이 달라질 수 있다. 때문에 재사용성이 떨어지는 결과를 낳게 된다!

마무리! 🏄🏻‍♂️

  • ContextAPI를 처음 알게 되었을 때, 전역상태 관리 라이브러리 대신 쓸 수 있다! 라고 들었어서, ContextAPI를 마치 redux처럼 생각했었다. 그래서 어떻게 initialState를 만들지? 같은 고민을 하다가 지난 개인 프로젝트에서 결국 ContextAPI를 제대로 활용하지 못했었다. 그러나 처음부터 차근차근 공부하다보니, 내가 컨셉을 좀 잘 못 이해하고 있었다는 걸 알게 됐다. 지금으로서 내 생각엔 ContextAPI와 Redux는 기능을 비슷할 지라도 컨셉은 많이 다르다.
  • 생각해보니, styled-component 사용할 때 다크모드 테마 관리하면서 이미 ContextAPI를 활용한 적이 있었다. 다만 이게 ContextAPI인지 모르고 활용했을 뿐...
profile
부정확한 정보나 잘못된 정보는 댓글로 알려주시면 빠르게 수정토록 하겠습니다, 감사합니다!

0개의 댓글