리액트를 다루는 기술 _ 15장
목요일은 역시 목요일이군.
15장. Context API
Context API는 React 프로젝트에서 전역적으로 사용할 데이터가 있을 때 유용한 기능!
(ex 로그인 정보, 애플리케이션 환경 설정, 테마 등)
여태 사용했던 stlyed-components, 리액트 라우터 등의 라이브러리도 Context API를 기반으로 구현되어 있다.
15-1) Context API를 사용한 전역 상태 관리 흐름 이해하기
그런데 전역 상태를 관리한다는 게 무슨 뜻?
지금껏 우리는 프로젝트 내에서 환경 설정, 사용자 정보와 같이 전역적으로 필요한 상태를 관리할 때
→ 주로 최상위 컴포넌트인 App의 state에 넣어 관리했다!
왼쪽 그림에 해당하는 건데...
App이 지닌 value를 F나 J에 전달하려면 여러 컴포넌트를 거치는 수밖에 없었다!
(App→A→B→F / App→A→B→E→G 과 같이..! 실재 Todo 만들 때도 이렇게 했다)
컴포넌트나 데이터가 많아질수록 코드가 복잡해지는 건 당연지사.
따라서 오른쪽 그림과 같이 Context를 만들어 단 한 번에 원하는 값을 받아와 사용할 수 있도록!
15-2) Context API 사용법 익히기
일단 context-tutorial이라는 프로젝트를 생성하자.
1. 새 Context 만들기
src 디렉터리에 contexts라는 폴더를 만들고, color.js 파일을 생성!
color.js
새 Context를 만들 때는 React의 createContext 함수를 사용한다.
파라미터에는 해당 Context의 상태를 지정!
다른 컴포넌트가 이 context를 가지려면 해당 컴포넌트 상위에 context를 정의한 변수(ColorContext)를 감싸면 된다.
Context에는 Provider와 Consumer 두 개의 컴포넌트가 존재한다.
Provider은 Context에서 사용할 값을 설정할 때 사용되고,
(이때 전달받는 컴포넌트의 제한 수는 없음)
Consumer은 설정한 값을 불러와야 할 때 사용된다.
2. Consumer 사용하기
다음으로 ColorBox란 컴포넌트를 만들어 ColorContext 속 색상을 보여주자.
이때 색상을 props로 받아오는 것이 아닌...
ColorContext 안에 들어 있는 Consumer라는 컴포넌트를 통해 색상을 조회한다!
components/ColorBox.js
Consumer 사이 중괄호를 열어 그 안에 함수를 넣어 줬다! (Consumer의 자식은 항상 함수여야 함)
이러한 패턴을 Function as a child 혹은 Render Props라고 하는데...
컴포넌트의 children이 있어야 할 자리에 일반 JSX나 문자열이 아닌, 함수를 전달한 것이다.
Context가 잘 뜨는 모습.
3. Provider 사용하기
Provider을 사용하면 Context의 value를 변경할 수 있다!
App.js
App 컴포넌트를 이렇게 수정해주자!
쨘. 정의한 Context가 변경된 모습.
즉 createContext 함수를 사용할 때 지정한 기본값은 Provider를 사용하지 않았을 때 적용된다!
이때 Provider를 전달하는 변수는 반드시 value를 사용해야 하며,
Provider는 사용했는데 value를 명시하지 않으면 기본값을 사용하지 않아 오류가 발생한다.
15-3) 동적 Context 사용하기
고정적인 값 대신, Context의 값을 업데이트하려면 어떻게 해야 할까?
1. Context 수정하기
Context의 value에는 무조건 상태 값만 있어야 하는 건 아니다 → 함수도 전달 가능!
기존에 작성했던 ColorContext를 수정해보자.
contexts/color.js
ColorProvider라는 컴포넌트를 새로 만들었고, 여기에서 ColorContext.Provider를 렌더링하고 있다.
그런데 여기서 props로 받아온 {children}의 정체는 뭘까?
기본적으로 children은 부모 컴포넌트의 태그 사이에 있는 녀석들 몽땅을 지칭한다.
즉 부모인 App 컴포넌트에서 ColorProvider 사이에 있는 div를 뜻하는 것.
얘를 받아온 후 return에 {children}으로 렌더링함으로써,
Django base.html의 block처럼 해당 부분에 구멍을 뚫어 가져온 것이라 생각하면 되겠다.
이 Provider의 value에는 상태는 state로, 업데이트 함수는 actions로 묶어서 전달.
이를 통해 Context에서 값을 동적으로 사용할 수 있게 된 것!
(반드시 state와 actions를 묶어줄 필요는 없지만, 따로 분리해주면 이후 다른 컴포넌트에서 Context를 사용할 때 편하다)
추가적으로 createContext를 사용할 때 기본값으로 사용할 객체도 수정!
이때 createContext의 기본값은 실제 Provider의 value에 넣는 객체 형태와 일치시키는 게 좋다.
(Context 코드를 파악하기도 쉬워지고, Provider를 실수로 사용하지 않았을 때 에러 발생도 막는단다)
App.js
App 컴포넌트도 이렇게 수정!
ColorBox.js
이렇게 바꿔줄 수 있다.
Consumer를 사용했으니 value를 통해 해당 Context 속성들을 사용할 수 있을 테고,
여기서는 그중에서도 .state.color / .subcolor로 현재 지정된 값들을 적용시킨 것!
Consumer 자식은 무조건 함수!
실수로 끝에 ; 를 붙였더니 render is not a function react context 에러가 뜨고 말았다!(한참이나 끙끙...)
똑같이 쳤다면 이렇게 뜬다!
2. 색상 선택 컴포넌트 만들기
이번에는 Context의 actions에 넣어 준 함수를 호출해보자. SelectColors라는 컴포넌트를 생성!
component/SelectColors.js
App에 렌더링 하면 색 별 상자들이 뜨겠지?
쨔라란.
이제 기능을 추가!
마우스 왼쪽 클릭을 하면 Color를, 오른쪽 클릭은 하면 subColor를 바꿔주고자 한다.
비구조화 할당을 통해 { value }를 전달받고, value.actions.setColor&subColor를 사용하지 않고,
{ actions }를 전달받고 actions.setColor&subColor 형태로 사용!
아차차. 마우스 오른쪽 버튼 클릭 이벤트는 onContextMenu를 사용하면 된다!
(이때 e.preventDefault()를 통해 원래 이벤트(브라우저 메뉴 뜨는 것)가 발생하지 않도록)
그럼 클릭할 때마다 색이 잘 바뀌는 걸 볼 수 있다.
15-4) Consumer 대신 Hook 또는 static contextType 사용하기
Context에 있는 값을 사용할 때, Consumer 대신 다른 방식을 사용해 값을 받아와 보자!
1. useContext Hook 사용하기
우리의 친구 Hooks 중 useContext라는 Hook을 사용하면, 함수형 컴포넌트에서 Context를 편하게 쓸 수 있다.
ColorBox.js
...? 코드가 한결 간단해졌다.
children에 함수를 전달하는 Render Props 패턴이 불필요하다면,
useContext Hook을 사용해 훨씬 편하게 Context 값을 조회할 수 있다.
(즉 현재 우리는 ColorContext 속 state만 필요하니까!)
2. static contextType 사용하기
얘는 클래스형 컴포넌트에 해당!
(useContext는 당연히 함수형 컴포넌트에만 사용된다)
Context API를 통해 무조건 부모 → 자식 흐름으로 props를 전달하지 않고, 더욱 쉽게 상태 교류가 가능해졌다.
당연히 작은 규모의 프로젝트에서는 오히려 불필요하겠지만...
전역적으로 여기저기서 사용되는 상태가 있고, 컴포넌트 개수가 많은 상황이라면 Context API를 활용하는 게 좋겠다.
다음 장은 Redux라는 녀석인데...
이 라이브러리도 Context API 기반으로 만들어져 있으며, 전역 상태 관리를 도와준다.
단순한 상태 관리는 Context API만으로도 가능하지만...
Redux는 더욱 향상된 성능과 미들웨어 기능, 개발자 도구, 높은 유지 보수성을 제공한다는 강점이 있다!