[React] useContext

Nick·2022년 5월 10일
1

React Hooks

목록 보기
1/1

React를 사용해 프로그래밍을 하다 보면, 상위 Component의 상태를 하위 Component에 전달해야될 때가 있다.

문제는 리액트 컴포넌트가 Tree 구조로 이뤄져있어, 목표 Component에 도달하기 위한 depth가 깊다면 필연적으로 props drilling 문제를 마주하게 된다.

//이해를 돕기위한 간단한 예시입니다.

function Header({userId}) {
  return <Title userId={userId} />
}

function Title({userId}) {
  return <Content userId={userId} />
}

function Content({userId}) {
  return <Main userId={userId} />
}
  
function Main({userId})...

위의 예제 코드에서 실질적으로 userId를 사용하는 것이 Main Component뿐이라면, 중간에 위치한 Component들은 userId를 알 필요도 전달받을 필요도 없다.
실수로 인해 중간에서 userIdData가 바뀔 위험도 있고, 이렇게 문제가 발생하면 어디에서 문제가 발생했는지 일일히 찾아보아야 한다.

또, 변경사항이 생길 경우 모든 Component를 수정해야 한다.
매우 비효율적이고 위험도가 높은 일이다.

이를 해결해줄수 있는 것이 바로 Context APIuseContext Hook이다.
이것들이 어떻게 문제를 해결해줄수 있는지 원리를 알아보고, 하나씩 사용법을 알아보자.

그 전에

공식 문서는 이렇게 설명하고 있다.

context는 React 컴포넌트 트리 안에서 전역적(global)이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 
방법입니다.
그러한 데이터로는 현재 로그인한 유저, 테마, 선호하는 언어 등이 있습니다.
context의 주된 용도는 다양한 레벨에 네스팅된 많은 컴포넌트에게 데이터를 전달하는 것입니다. 
context를 사용하면 컴포넌트를 재사용하기가 어려워지므로 꼭 필요할 때만 쓰세요.
여러 레벨에 걸쳐 props 넘기는 걸 대체하는 데 context보다 컴포넌트 합성이 더 간단한 해결책일 수도 있습니다.

여기서 주목해야 하는 부분은 2곳 인데,

첫번째 전역적 데이터를 공유
두번째 재사용성이 떨어진다

예를 들어 Dark Mode를 지원하는 앱에서 isDarkMode 라는 state의 값이 True일 때 Component들의 Style이 바뀐다고 하면 화면에 표시되는 모든 ComponentisDarkMode라는 state를 알고 있어야 한다.
isDarkModeTrue일 때 DarkMode에 맞는 style로 바꿔주기 위해서다.
때문에 isDarkMode는 전역적인 Data로 존재해야 하고 Context를 사용하기에 알맞은 Data이다.

두번째 재사용성이 떨어진다 는 부분은 Context API를 사용하기 위해 필요한 Provider에 대한 이해가 필요하다.
자세한 사용법은 뒤에서 다루고 간단한 예시를 들어 설명하겠다.

// timecontext.js

// 값을 할당해주지 않았다.
export const TimeContext = createContext(null);

// App.js

function App() {
  const [time,setTime] = useState(1);
  return (
    // value를 통해 값을 할당해주었다.
    <TimeContext.provider value={{time,setTime}}>
    	<Header />
    <TimeContext.provider />
  )
}

// header.js

function Header () {
  // useContext를 통해 DATA를 가져온다.
  const { time, setTime } = useContext(TimeContext);
  // setTime함수를 이용해 state값 수정
  const handleTime = () => {
    setTime(time + 1)
  };
  //state 값 사용
  return (
    <button onClick={handleTime}>
    {time}
	</button>
  )
}

위의 코드는 추후에 다른 곳에서 재활용하기 어렵다.
provider를 통해 감싸져 있어야하고, contextData가 이전에 사용된 것과 일치하지 않으면 동작하지 않을 것이기 때문이다.

공식 문서의 내용중 가장 중요하다고 생각하는 2가지를 간단하게 설명해보았다.
이 뒤로는 간단하게 Context API에 대한 설명과 useContext Hook 사용법을 다루어 보도록 하겠다.

Context API && useContext

일반적인 React 애플리케이션에서 데이터는 위에서 아래로 (즉, 부모로부터 자식에게) props를 통해 전달되지만, 애플리케이션 안의 여러 컴포넌트들에 전해줘야 하는 props의 경우 (예를 들면 선호 로케일, UI 테마) 이 과정이 번거로울 수 있습니다. context를 이용하면, 트리 단계마다 명시적으로 props를 넘겨주지 않아도 많은 컴포넌트가 이러한 값을 공유하도록 할 수 있습니다.

context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있습니다.

React Context 공식문서
언제나 API에 대한 설명은 그 API를 만든 사람들의 글을 읽는 것이 가장 좋다!

Context API는 전역적으로 필요한 Data를 중첩이 많이 된 Component Tree에서 보다 쉽게 전달하기 위한 API이다.
위의 공식문서에 들어가 좀 더 자세한 설명을 읽어보면 좋을 것 같다.


이제 사용법을 알아보기 위해 위의 예제 코드를 가져와 자세히 살펴보자.

먼저 Context를 만들기 위해 createContextImport 해준다.
그 다음 TimeContext에 새로운 Context를 만들어 할당해준다.

이 때 createContextParameter로 들어가는 값을 defaultValue라고 부르는데, 공식문서에서는

defaultValue 매개변수는 트리 안에서 적절한 Provider를 찾지 못했을 때만 쓰이는 값입니다. 이 기본값은 컴포넌트를 독립적으로 테스트할 때 유용한 값입니다. 
Provider를 통해 undefined을 값으로 보낸다고 해도 구독 컴포넌트들이 defaultValue 를 읽지는 않는다는 점에 유의하세요.

라고 설명되어 있다.

본 예제는 nulldefaultValue로 설정했는데, 적절한 값을 찾지 못하는 오류등이 발생했을 경우 확실하게 파악하기 위함이다.

이어서 설명하자면, 생성된 contextexport하여 내보낸다.

// context.js

// createContext를 import한다.
import { createContext } from 'react';
// 새로운 context를 만들어 TimeContext 변수에 할당해준다.
export const TimeContext = createContext(null);
//export를 통해 내보내준다.

그리고 최상위 컴포넌트 파일에서 contextimport하여 가져온다.
component의 가장 바깥쪽에 아까 만든 context이름.provider를 통해 component들을 감싸준다.

provider는 하위 Component들이 자신의 상위에 존재하는 provider를 구독(쉽게 말해 그 값을 사용)할 수 있게 해준다.
(styled-component등을 사용해 보았다면 익숙할 것이다. ThemeProvider도 비슷한 원리로 동작하는 것 같다.)

이 때 providervalue라는 속성을 가지는데 이 valuecontextData가 된다.
value로는 어떤 값이든 올 수 있다.

ex) 함수, 객체, 배열, 문자열, 숫자 ...

여기서는 time이라는 state와, 그것을 관리하는 setTime을 한꺼번에 할당하기 위해 중괄호로 감싸주었다.

// App.js

//import하여 가져옴
import { TimeContext } from './context';

function App() {
  const [time,setTime] = useState(1);
  return (
    //context.provider를 통해 component들을 감싸준다.
    //이로써 TimeContext.provider 하위에 위치하는 component들은 TimeCotext를 구독하게 된다.
    <TimeContext.provider value={{time,setTime}}>// 값을 할당해 주었다.
    	<Header />
    <TimeContext.provider />
  )
}

이제 context를 사용하여 component를 완성해보자.
먼저 TimeContext를 사용하기 위해 import 해온다.
그 다음 useContext를 사용하기 위해 마찬가지로 import 해온다.

이제 TimeContext의 값을 사용하기 위해 useContext를 사용해 값을 가져온다.
구조분해할당을 이용해 timesetTime을 한번에 할당해주었다.
이후 편하게 사용하면 된다.

// import
import { TimeContext } from './context';
import { useContext } from 'react';

function Header () {
  // useContext를 통해 DATA를 가져온다.
  const { time, setTime } = useContext(TimeContext); // 값을 할당해준다.
 
  const handleTime = () => {
    setTime(time + 1)// setTime함수를 이용해 state값 수정
  };
 
  return (
    <button onClick={handleTime}>
    {time}//state 값 사용
	</button>
  )
}

마치며

여기까지 useContext에 대한 기초적인 설명을 해보았다.
학습한 내용을 정리하며 작성한 글인데 작성하면서 나 자신도 이전보다 더 잘 이해하게 된 것 같아 뿌듯하다.

본문에 따로 작성하지 않았지만, providervalue prop이 바뀔때 마다 Context를 구독하고 있는 모든 Component가 다시 렌더링된다.

이를 최적화하는데 도움이 되는 글을 아래에 남기며 글을 마친다.

최적화를 위한 DongKyun Ko님 블로그

참조 : Stark Jeon님 블로그

profile
-FE 꿈나무

0개의 댓글