Redux없이도 state관리를 해줄 수 있는데 그것이 context이다.
한편으로, Redux처럼 React 컴포넌트 트리 안에서 global하게 데이터를 공유할 수 있다.
context의 주된 용도는 다양한 레벨에 nesting된 많은 컴포넌트에게 data를 전달하는 것이고 context를 사용하면 component를 재사용하기 어려워지므로 꼭 필요할 때만 써야 한다.
사용법
const MyContext = React.createContext(defaultValue);
이렇게 Context 객체를 만든다. Context 객체를 구독하고 있는 컴포넌트를 렌더링할 때 React는 트리 상위에서 가까이에 있는, 짝이 맞는 Provider로부터 현재값을 읽는다.
DefaultValue
매개변수는 트리 안에서 적절한 Provider를 찾지 못했을 때만 쓰이는 값이다. 컴포넌트를 독립적으로 테스트 할 때 유용한 값이기도 하다.
사용법
<MyContext.Provider value={어떤 값}>
Context 객체에 포함된 Provider는(=React 컴포넌트) context를 구독하는 컴포넌트들에게 context의 변화를 알리는 역할을 한다.
Provider는 value prop을 받아서 이 값을 하위에 있는 컴포넌트에게 전달한다. 여기서 값을 전달받을 수 있는 컴포넌트의 수에 제한은 없다. Provider 하위에 또 다른 Provider를 배치하는 것도 가능하며, 이렇게 되면 하위에 있는 Provider의 값이 우선시된다.
Provider 하위에서 context를 구독하는 모든 컴포넌트는 Provider의 value prop
가 바뀔 때 마다 다시 렌더링
된다. Provider로부터 하위 consumer (.contextType
와 useContext
포함)로 전파할 때 shouldComponentUpdate 메서드가 적용되지 않으므로, 상위 컴포넌트가 업데이트를 건너뛰더라도 consumer가 업데이트 된다.
context의 값이 바뀌었는지 여부는 Object.is
와 동일한 알고리즘을 사용해 이전 값과 새로운 값을 비교하면 된다.
Object.is() 메서드는 두 값이 값은 값인 지 결정한다.
Object.is(value1, value2) => 결과 Boolean.
다시 렌더링할지 여부를 정할 때 잠조를 확인하기 때문에 Provider의 부모가 렌더링 될 때마다 불 필요하게 하위 컴포넌트가 다시 렌더링 되는 문제가 생길 수도 있다. 아래 코드에서 value가 바뀔 때마다 매번 새로운 객체가 생성되므로 Provider가 렌더링 될 때마다 하위에서 구독하고 있는 컴포넌트 모두가 다시 렌더링 될 것이다.
const App extends React.Component {
render () {
return (
<MyContext.Provider value={{something: 'something'}}>
<Toolbar />
</MyContext.Provider>
)
}
}
이를 피하기 위해서 값을 부모의 state로 끌어올리자.
const App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'},
};
}
render(){
return(
<Provider value={this.state.value}>
<Toolbar />
</Provider>
)
}
}
context 한개 구독하기
class MyClass extends React.Component {
componentDidMount () {
let value = this.context;
MyContext의 값을 이용한 코드
}
componenDIdUpdate(){
let value = this.context;
//
}
componentWillUnmount () {
let value = this.context
//
}
render (){
let value = this.context;
//
}
}
MyClass.contextType = MyContext;
context 여러개 구독하기
const ThemeContext = React.createContext('light');
const UserContext = React.createContext({
name: 'Guest',
})
class App extends React.Component {
render(){
const {signedInUser, theme} = this.props;
return(
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
)
}
}
function Layout(){
return(
<div>
<Sidebar />
<Content />
</div>
)
}
//여러 context의 값을 받는 컴포넌트
function Content() {
return(
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
)
}
이렇게 둘 이상의 context 값이 함꼐 쓰이는 경우가 많다면 그 값들을 한 번에 받는 render prop 컴포넌트를 만드는 것도 고려해보자.
설명
Context객체를 원하는 Class의 contextType 프로퍼티로 지정할 수 있다. 그러면 그 Class안에서 this.context
를 이용해 해당 Context의 가장 가까운 Provider를 찾아 그 값을 읽을 수 있게 된다. 이 값은 render를 포함한 모든 lifecycle 메서드에서 사용할 수 있다.
사용법
<MyContext.Consumer>
{value => context 값을 이용한 렌더링 }
</MyContext.Consumer>
설명
context 변화를 구독하는 React컴포넌트이다.
Context.Consumer의 자식은 함수
여야 하고 함수 컴포넌트 안에서 context를 읽기 위해서 쓸 수 있다. 이 함수를 context의 현재값을 받고 React node값을 반환한다. 이 함수가 받는 value 또한 가장 가까운 상위 Provider의 value prop과 같다. 상위 Provider가 없다면 당연히....defaultValue와 동일할 것이다.
Context 객체는 displayName
문자열 속성을 설정할 수 있다. React 개발자 도구는 이 문자열을 사용해서 context를 어떻게 보여줄 지 결정한다.
예를 들어 아래 컴포넌트는 개발자 도구에 MyDisplayName으로 표시된다.
const MyContext = React.createContext(어떤 value);
MyContext.displayName = 'MyDisplayNmae';
<MyContext.Provider>//MyDisplayName.Provider 라고 뜬다.
<MyContext.Consumer>//MyDisplayName.Consumer 라고 뜬다.