개발/React

[React] Context API 개념과 사용법, 학습하며 겪은 어려움

알파카털파카 2023. 2. 1. 00:33
Context API

 

 

이번주에는 원티드 프리온보딩 강의에서 배운 context api를 학습했다.

[원티드 프리온보딩 프론트엔드 인턴십] Week 3-1. React Hook의 심층 활용 - 강의 후기

 

[원티드 프리온보딩 프론트엔드 인턴십] Week 3-1. React Hook의 심층 활용 - 강의 후기

Week 3-1. React Hook의 심층 활용 의존성 배열 / useEffect / React.memo / useCallback / useMemo / Context API 2주차 강의는 원티드 커리어 챌린지 로 대체되었다. 커리어 챌린지는 2주 동안 진행된 전 직군 대상 강의

shinjungoh.tistory.com

 

context api를 학습해야겠다고 생각한 이유는 첫째로, 프로젝트가 조금만 복잡해져도 프롭스 드릴링 문제를 자주 겪었는데, context api가 이를 해결하는데 도움이 되기 때문이었고 둘째로, 리덕스를 학습하기 위해서였다.

 

원티드 프리온보딩 4주차 과제(feat: Week 4 과제 후기)를 하면서 리덕스를 잘 사용하지 못해 개인 과제를 제대로 마치지 못했기 때문에 리덕스에 대한 갈망(?)이 있었다. 무릇 개발자라 하면, 기술을 직접 사용해보고 어땠는지 주관을 가질 수 있어야 참된 개발자라고 생각한다. 리덕스가 context api를 기반으로 한 라이브러리이기에 리덕스를 잘 이해하기 위해서는 context api를 제대로 짚고 넘어가야겠다고 판단해 이번 기회에 학습을 하게 됐다.

평소 인강을 위주로 학습을 해왔는데 이번에는 공식문서와 서적을 중심으로 학습을 진행했다. 공식문서와 친해져야 한다는 말이 계속 맴돈다. 

 

이번 글에는 1️⃣ context api가 무엇인지 개념과, 2️⃣ 사용방법, 3️⃣ 학습하며 겪은 어려움을 적어볼 것이다.

 

 


 

1. Context API란?

Context API

 

Context API란, React에서 제공하는 내장 API  
컴포넌트들에게 동일한 context(맥락)를 전달하고, 한 번에 값을 받아와서 사용  
Context API를 기반으로 한 라이브러리에는 리덕스, 리액트 라우터, styled-components 등이 있다.

 

 

⚠️ Context API는 프롭스 드릴링을 피하기 위해 사용하는 것이지, 전역 상태관리가 아니다!  

* 여러 컴포넌트에 동일한 값을 접근할 수 있도록 만들어주는 api(통로의 개념)
* 자주 변경되는 데이터를 관리하려면 용도에 맞게 구분해서 사용하면 됨

 

 

React에서 데이터를 전달하는 기본 원칙, 단방향성

 

단방향성은 애플리케이션의 안전성을 높이고 흐름을 단순화하는데 유용하다.
React 애플리케이션에서 데이터는 위에서 아래로(👨‍👩‍부모 -> 👧자식) props를 통해 전달된다.
Context API를 컴포넌트에게 의존성을 주입하는 용도로 사용할 수 있음

 

Props Drilling(프롭스 드릴링) 

 

하지만 단방향성이라는 특성 때문에 문제가 발생하기도 한다.

props를 애플리케이션 안의 여러 컴포넌트에 전해줘야하는 경우에 프롭스 드릴링이 발생하는데, 예시는 다음과 같다.

1. 너무 많은 컴포넌트 단계를 거쳐야 함
2. 중간 컴포넌트가 props를 이용하지 않아도 넘겨줘야 함
3. 형제 관계의 컴포넌트 간의 props 전달이 복잡

데이터가 많아지고, 컴포넌트를 더 많이 거쳐야 하는 경우 ⚠️유지 보수성이 낮아질 수 있다.

 

 

 

 

2. Context API 사용법

createContext

 

새로운 Context 객체 생성

import { createContext } from "react";

const MyContext = createContext(defaultValue);


* defaultValue 매개변수는 Provider가 없을 때만 사용됨
* Provider를 통해 undefined를 값으로 보내도, 구독 컴포넌트가 defaultValue를 읽지는 않음

 

 

 

Context.Provider

 

Context의 value를 변경 가능한 React 컴포넌트, 값을 제공하는 역할

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


Provider 컴포넌트는 value prop을 받아서 값을 하위 컴포넌트에게 전달  
* Provider 하위에 또 다른 Provider를 배치하는 것도 가능 
    * 이 경우 하위 Provider의 값이 우선시
* Provider 하위에서 context를 구독하는 모든 컴포넌트는 Provider의 value가 바뀔 때마다 리렌더


📌Provider 사용 시 value 값을 명시해야 함
* createContext 함수에서 파라미터로 넣은 기본값은 Provider를 사용하지 않았을 때만 사용됨 
* Provider는 기본값을 사용하지 않기 때문에 value가 없으면 오류 발생

value 값이 없을 때 오류 발생

 

 

 

Context.Consumer 


함수 컴포넌트안에서 context 변화를 구독하는 React 컴포넌트, 값을 소비하는 역할

<MyContext.Consumer>
    {value => /* context 값을 이용한 렌더링, Provider에서 받은 값 */}
</MyContext.Consumer>


Consumer 사이에 { 함수 } 전달
  * 이 패턴을 function as a child 또는 Render Props 라고 부름 
  * 이 함수는 context의 현재값을 받고 React 노드를 반환 

  * 이 함수가 받는 value 값은 가장 가까운 상위 Provider의 value prop과 동일

  * 상위에 Provider가 없다면 value 매개변수 값은 createContext의 defaultValue와 동일  

 

 

 

useContext

 

context 객체를 받아 그 context의 현재 값을 반환, context 값을 편리하게 조회
💡 context의 값에 접근할 때, Consumer를 사용하지 않아도 됨

const value = useContext(MyContext);

// 틀린 사용: useContext(MyContext.Consumer)
// 틀린 사용: useContext(MyContext.Provider)
// 클래스
static contextType = MyContext 또는 <MyContext.Consumer>와 동일

클래스 메소드에서도 context에 넣어 둔 함수 호출 가능
한 클래스에서 하나의 context만 사용 가능

 

useContext를 호출한 컴포넌트는 context 값이 변경되면 항상 리렌더링 발생

  * 메모이제이션을 사용하여 최적화 가능

 

context의 현재 값은 Hook을 호출하는 컴포넌트에 가장 가까운 <MyContext.Provider>의 value prop에 의해 결정

  * <MyContext.Provider>가 갱신되면 Hook은 provider에게 전달된 가장 최신의 context value를 사용하여 렌더

  * 상위 컴포넌트에서 React.memo 또는 shouldComponentUpdate를 사용하더라도 useContext를 사용하고 있는 컴포넌트 자체에서부터 다시 렌더링

 

 

 

 

3. 학습하며 겪은 어려움

context api를 학습하면서 어려움을 겪은 부분이 있었다. 하위 컴포넌트 간 값이 어떻게 공유되는지 개념이 머릿속에 잡히지 않았다.

 

App.js

 

App.js 위에 동등하게 렌더링된 SelectColors 컴포넌트와 ColorBox 컴포넌트가 어떻게 서로 바뀐 값을 공유하는지 이해가 되지 않았다.

 

 

📌 이 스크린샷에서 윗 부분의 무지개색 네모를 그려주고 있는 컴포넌트가 SelectColors.js이다.

 

onClick 함수로 context의 값을 바꿔준다.

 

📌 아래 부분의 큰 네모와 작은 네모를 그려주는 컴포넌트가 ColorBox.js이다.

 

SelectColors에서 좌클릭, 우클릭한 색상이 각각 큰 네모와 작은 네모에 표시된다. 색깔의 state 값은 context에서 받아온다. 

 

 

 

이해가 어려웠던 부분은 두 컴포넌트가 어떻게 서로 바뀐 값을 알고 있는가 였다. 노트에 그림을 그려서 이해해보려고 노력했다. 

내가 놓치고 있던 부분은, context를 app.js처럼 최상위 컴포넌트로 생각하고 있었다는 점이었다. 이름만 context로 바뀌었지, 단방향으로 props를 전달하는 최상위 컴포넌트와 동일한 역할을 하고 있는 것처럼 받아들이고 있었다. 그러면 결국 컨텍스트를 쓰는 이유가 없어지는 것이 아닌가?! 여기서 깨닫고 다시 고민해 보았다.

 

쉽게 알아듣기 위해 비유를 들어서 이해했다. 우리집 와이파이(컨텍스트)는 우리집 안에서만 쓸 수 있다. 남의 집에서 쓸 수 없다. 나는 우리집의 침실, 부엌, 화장실 등 그 어떤 곳에서도 우리집 와이파이를 사용할 수 있다. 내가 침실에서 와이파이 비밀번호를 변경하면, 화장실에서 와이파이를 이용하려고 할 때 바뀐 비밀번호를 입력해야 한다. 장소가 바뀌었다고해서 장소마다 특정 값이 고정되어 있는 것이 아니다. 하나의 값을 공유하고 있다.

 

다시 색깔 선택하기 문제로 되돌아 가보자.

1. SelectColors 컴포넌트에서 onClick 이벤트를 통해 context의 state 값을 바꾼다. 

2. 바뀐 값이 context에 반영된다.

3. ColorBox 컴포넌트에서 context의 변경된 값을 받아 화면에 변경된 색상을 그려준다.

 

결국 나는 context api의 핵심 개념과 친숙하지 않아 자꾸 App.js의 props 전달 방식으로 생각했기에 어려움을 겪은 것이다. 아직 완벽하진 않지만 대략 이런 개념이구나를 알게 된 고민의 시간이었다. 

 

링크는 <리액트를 다루는 기술> 책의 예제를 보고 학습한 색상 선택하기 문제의 해당 커밋이다.

feat: Context 값 동적 업데이트

feat: Context API 색상 선택하기 기능 구현

 

 

 

참고 사이트, 서적


https://ko.reactjs.org/docs/context.html  
https://ko.reactjs.org/docs/render-props.html  

https://ko.reactjs.org/docs/hooks-reference.html#usecontext

<리액트를 다루는 기술>, 김민준