ContextAPI
개요
react는 16.3 버전부터 정식적으로 ContextAPI를 지원하고 있다.
일반적으로 부모와 자식간 props를 전달해 state를 변화시키는 것과는 달리 ContextAPI는 컴포넌트 간 간격이 없다.
즉, 컴포넌트를 건너뛰고 다른 컴포넌트에서 state, function을 사용할 수 있다.
또한 redux의 많은 어려운 개념보다 context api는 Provider, Consumer, createContext 개념만 알면 적용할 수 있다.
ContextAPI는 컴포넌트안에서 로그인데이터 웹 내 사용자의 설정파일 테마 언어 등 전역적으로 공유되는 데이터로 사용하면 좋다.
Context
React에서 props와 state는 부모 컴포넌트와 자식 컴포넌트 또는 한 컴포넌트 안에서 데이터를 다루기 위해 사용된다.
이 props와 state를 사용하게 되면 부모 컴포넌트에서 자식 컴포넌트, 즉, 위에서 아래, 한쪽으로 데이터가 흐르게 된다.
만약 다른 컴포넌트에서 한쪽으로 흐르고 있는 데이터를 사용하고 싶은 경우 또는 다른 컴포넌트에서 사용하고 있는 데이터를 현재의 데이터 흐름에 넣고 싶은 경우가 발생한다면 어떻게 해야할까?
React에서 데이터는 위에서 아래로 흐르게 되므로 사용하고 싶은 데이터와 이 데이터를 사용할 컴포넌트의 공통 부모 컴포넌트에 state를 만들고 사용하고자 하는 데이터를 props로 전달하면 이 문제를 해결할 수 있다.
하지만 이처럼 컴포넌트 사이에 공유되는 데이터를 위해 매번 공통 부모 컴포넌트를 수정하고 하위 모든 컴포넌트에 데이터를 props로 전달하는 것은 매우 비효율적이다.
이와 같은 문제를 해결하기 위해 React에서는 Flux라는 개념을 도입하였고 그에 걸맞은 Context API를 제공하기 시작했다.
React의 props전달법
Context는 부모 컴포넌트로부터 자식 컴포넌트로 전달되는 데이터의 흐름과는 상관없이 전역적인 데이터를 다룰 때 사용한다.
전역 데이터를 Context에 저장한 후, 데이터가 필요한 컴포넌트에서 해당 데이터를 불러와 사용할 수 있다.
React에서 Context를 사용하기 위해서는 Context API를 사용해야 하며, Context의 Provider와 Consumer를 사용해야 한다.
Context에 저장된 데이터를 사용하기 위해서는 공통 부모 컴포넌트에 Context의 Provider를 사용하여 데이터를 제공하고, 데이터를 사용하려는 컴포넌트에서 Context의 Consumer로 데이터를 사용할 수 있다.
API 종류
React.createContext
const MyStore = React.createContext(defaultValue);
- context 객체를 만든다.
- 컴포넌트가 이 context를 가지려면 해당 컴포넌트 상위에 provider로 부터 context를 정의한 변수 myStore를 감싸면 된다.
- defaultValue 의 param은 트리 안에 적절한 provider를 찾지 못했을 때 쓰이는 값이다.
또한, 해당 store가 어떠한 provider에 할당되지 않은 경우 완전 독립적인 context를 유지할때 쓰인다. - provider를 통해 undefined를 보낸다 해도 해당 context를 가진 컴포넌트는 provider를 읽지 못한다.
Context.Provider
<MyStore.Provider value={this.state}>
<subComponent1 />
<subComponent2 />
</MyStore.Provider>
- provider는 정의한 context를 하위 컴포넌트에게 전달하는 역할을 한다.
- provider를 전달하는 변수는 꼭 value를 사용해야 한다.
- 전달 받는 컴포넌트의 제한 수는 없다.
- provider에 하위 provider가 배치 가능하며 그럴경우에는 하위 provider값이 우선이 된다.
- provider하위에 context를 가진 component는 provider의 value로 가진 state가 변화할 때마다 전부 리렌더링된다.
Context.Consumer
<MyContext.Consumer>
{value => /* context 값을 이용한 렌더링 */}
</MyContext.Consumer>
- context의 변화를 구독하는 컴포넌트다.
- context의 자식은 함수(컴포넌트)여야 한다.
- 이 함수(컴포넌트)가 가지는 context 값은 가장 가까운 provider의 값이 된다.
- 상위 provider가 없다면 createContext()에서 정의한 defaultValue를 가지게 된다.
ContextAPI 예시
createContext
// store.js
import React from "react";
const Store = React.createContext(null);
export default Store;
// rootStore.js
import React from "react";
const rootStore = React.createContext({ zxc: "testsetst" });
export default rootStore;
provider
// App.jsx
import React, { Component } from "react";
import Test1 from "components/Test";
import Test2 from "components/Test2";
import Test3 from "components/Test3";
import Store from "store";
export default class App extends Component {
constructor(props) {
super(props);
this.changeMessage = () => {
const { message } = this.state;
if (message === "hello") {
this.setstate({
message: "by"
});
} else {
this.setstate({
message: "hello"
});
}
};
this.state = {
test: "testContext",
message: "hello",
changeContext: this.changeMessage
};
}
render() {
const { test } = this.state;
return (
<Store.Provider value={this.state}>
<Test test={test} />
<Test2 />
<Test3 />
</Store.Provider>
);
}
}
normal consumer
//Test.jsx
import React, { Component } from "react";
import PropTypes from "prop-types";
import Store from "store";
export default class Test extends Component {
render() {
const { test } = this.props;
return (
<div>
<Store.Consumer>{store => store.message}</Store.Consumer>
<Store.Consumer>
{store => (
<button type="button" onClick={store.changeContext}>
changeContext
</button>
)}
</Store.Consumer>
{test}
</div>
);
}
}
Test.propTypes = {
test: PropTypes.string
};
Test.defaultprops = {
test: ""
};
2개의 store를 사용할 때
//Test.jsx
import React, { Component } from "react";
import rootStore from "rootStore";
import Store from "store";
export default class Test extends Component {
static contextType = rootStore;
componentDidMount() {
// rootStore context 구독
console.log(this.context);
}
render() {
return (
<>
{/* store context 구독 */}
<Store.Consumer>{store => store.message}</Store.Consumer>
<div>test</div>
</>
);
}
}
provider가 네스팅되어 상위 provider 값도 받아야할 때
//Test.jsx
import React, { Component } from 'react';
import Store from 'store';
import rootStore from 'rootStore';
// import PropTypes from 'prop-types';
class TT extends Component {
static contextType = rootStore;
componentDidMount() {
const { value } = this.props;
console.log('ttt', value);
console.log('root', this.context);
}
render() {
const { value } = this.props;
return <div>test3{value.message}</div>;
}
}
const Test = () => <Store.Consumer>{store => <TT value={store} />}</Store.Consumer>;
export default Test;
TT.propTypes = {
value: {},
};
TT.defaultprops = {
value: {},
};