React Context란?
React로 개발하다보면 부모 컴포넌트에서 자식 컴포넌트로 속성을 전달할 일이 많습니다. 자식 컴포넌트들이 단지 몇개에 불과하다면 문제가 되지 않지만 컴포넌트들의 중첩구조가 복잡해지면 얘기가 달라집니다.
[ 그림 1 : 복잡한 중첩구조 ]
App 컴포넌트에서 GreatGrandChild로 props를 이용해 전달하는 것은 대단히 번거롭고 힘든일입니다. App에서 GreatGrandChild로 이어지는 경로상의 컴포넌트를 props를 이용해 전달-전달-전달해야 하니까요.
**[ 그림2 : 속성-속성-속성을 거쳐 Leaf Node 컴포넌트로 전달 ]
React v16.3에는 이러한 문제를 해결할 수 있는 Context라는 구성요소가 확정되었습니다. 예전부터 기능이 있었지만 공식문서에도 빠져있었고, 사용하지 말라고 권고하던 것을 V16.alpha 버전에서 추가한 것입니다.
이전과 같으면 마지막 컴포넌트(GreatGrandChild)로 전달할 속성이라면 중간 과정에 있는 컴포넌트를 거쳐서 전달해야 했지만 Context를 사용하면 Leaf Node의 컴포넌트에서 Context에 직접 접근이 가능합니다.
[ 그림 3 : Context를 이용해 부모 컴포넌트의 상태에 직접 접근]
작성법
createContext를 이용해 Context 객체 작성
우선 React.createContext() 메서드를 이용해 Context 객체를 작성해야 합니다. 기본값을 줄 수 있지만 어차피 최상위 컴포넌트(App 컴포넌트)의 state를 연결하면 되므로 간단하게 작성해보겠습니다.
[ src/AppContext.js ]
import { createContext } from 'react';
const AppContext = createContext();
export default AppContext;
이제 state를 가지고 있는 App컴포넌트에 가서 다음과 같은 코드를 작성합니다.
**[src/App.js ]
import React, { Component } from 'react';
import Child from './Child';
import AppContext from './AppContext';
class App extends Component {
constructor(props) {
super(props);
this.state = { childMsg : "자식!", grandChildMsg:"손주", greatGrandChildMsg:"증손주" };
}
render() {
return (
<AppContext.Provider value={this.state}>
<div className="box">
<h1>App 컴포넌트</h1>
<Child />
</div>
</AppContext.Provider>
);
}
}
export default App;
App.js 컴포넌트에서는 자신의 state를 가지고 있으며 이것을 Child, GrandChild, GreatGrandChild로 전달하려 합니다. 이제는 속성을 이용하지 않고 Context를 이용해 하위 트리의 컴포넌트에서 vaue를 이용하도록 할 것입니다.
앞에서 작성해둔 AppContext를 임포트하여 <AppContext.Provider> 컴포넌트를 이용해 state 를 value로 지정한 후에 render 하도록합니다. 이제 하위 컴포넌트에서는 어디서나 value 정보를 이용할 수 있습니다.
이제 Child, GrandChild, GreatGrandChild를 살펴보겠습니다.
[src/Child.js]
import React, { Component } from 'react';
import GrandChild from './GrandChild';
import AppContext from './AppContext';
class Child extends Component {
render() {
return (
<AppContext.Consumer>
{(context)=> {
let msg = context.childMsg;
return (
<div className="box">
<h2>Child!! {msg}</h2>
<GrandChild />
</div>
)
}}
</AppContext.Consumer>
);
}
}
export default Child;
[src/GrandChild.js]
import React, { Component } from 'react';
import GreatGrandChild from './GreatGrandChild';
import AppContext from './AppContext';
class GrandChild extends Component {
render() {
return (
<AppContext.Consumer>
{(context)=> {
let msg = context.grandChildMsg;
return (
<div className="box">
<h3>Grand Child!! {msg}</h3>
<GreatGrandChild />
</div>
)
}}
</AppContext.Consumer>
);
}
}
export default GrandChild;
하위 트리의 컴포넌트에서 Context의 value를 이용하기 위해서 <AppContext.Consumer>와 같이 Consumer 컴포넌트를 이용해야 합니다. { (context)=> { return(...) } } 과 같이 Arrow Function을 이용해 직접 children을 render하는 방법을 사용하여 AppContext.Consumer의 context를 전달합니다. Arrow Function 내부에서 context로 전달된 값을 가공하거나 직접 이용하여 render할 수 있습니다. context.grandChildMsg 와 같은 형태로 말이죠.
[ 그림 4: 실행 결과 ]
이제까지 React v16.3에 추가된 Context API 기능의 사용법에 대해 알아보았습니다. 이 코드는 React V16.3에서만 가능하다는 점에 주의합시다. 현재시점(2018.02.08)에서 16.3 버전을 사용하기 위해서는 yarn add react@next react-dom@next와 같이 패키지를 다운로드하시고 사용해야 합니다.
Context API를 사용하면 동일 컨텍스트 하위 트리의 컴포넌트에서는 언제든 Context를 통해 공통의 정보를 접근할 수 있음을 알아보았습니다.
이 아티클에서 사용한 예제는 아래와 같습니다.
https://github.com/stepanowon/react_contextapi_example
다음은 조금 더 복잡한 구조의 연락처 앱 예제입니다. 이 예제에서 주목할 부분은 ContactList 컴포넌트에서 ContactItem를 반복적으로 렌더링할 때 입니다. 즉 ContactList 하위트리에 ContactItem 여러개가 렌더링되는 상황입니다. 이 경우에는 오히려 props(속성)으로 전달하는 편이 좋아보입니다. Context를 통해 전달되는 것은 연락처 전체 목록이기 때문입니다.
이 예제에서는 props로 키만을 전달하고 Context로 전달된 no를 받아서 find하여 개별 연락처를 바인딩하는 방법을 사용해보았습니다.
https://github.com/stepanowon/contactsapp_using_v163_context
필요한 기능이 추가 되었네요.
유용한 정보 감사합니다. ^^
이런 구조 복잡한데ㅠㅠ 항상 고민 하게 되는 상황이네요
정말 필요한 경우가 아닌데 써서 손해 보는 경우가 있을런지요?
아니면 속도면에서 이득을 보려고 쓰는 경우가 있을런지요?
todolist 같은 앱을 고려해보면 할일목록 여러건을 배열로 상태로 관리하고 이것을 context에 추가해주면 연락처 한건만을 렌더하는 컴포넌트(TodoItem 컴포넌트라고 이름지어보죠)에서는 사용하기 어렵습니다. 그래서 결국 고유 key를 속성으로 전달해준다음 todolist 데이터에서 find하거나 속성(props)으로 연락처 한건 전체를 TodoItem 컴포넌트로 전달해주는 방법을 사용해야 합니다. 결국 큰 메리트는 없어보입니다. 그래서 속성으로 전달하던 것을 Context로 대체하는 것은 무리가 있다는 느낌입니다. '전체앱에서 공통으로 사용할 간단한 값' 정도만 Context로 처리할 수 있지 않을까 생각합니다.