react는 16.3 버전부터 정식적으로 ContextAPI를 지원하고 있다.
일반적으로 부모와 자식간 props를 전달해 state를 변화시키는 것과는 달리 ContextAPI는 컴포넌트 간 간격이 없다.
즉, 컴포넌트를 건너뛰고 다른 컴포넌트에서 state, function을 사용할 수 있다.
또한 redux의 많은 어려운 개념보다 context api는 Provider
Consumer
createContext
개념만 알면 적용할 수 있다.
ContextAPI는 컴포넌트안에서
로그인데이터
웹 내 사용자의 설정파일
테마
언어
등 전역적으로 공유되는 데이터로 사용하면 좋다.
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로 데이터를 사용할 수 있다.
const MyStore = React.createContext(defaultValue);
provider
로 부터 context를 정의한 변수 myStore
를 감싸면 된다.defaultValue
의 param은 트리 안에 적절한 provider를 찾지 못했을 때 쓰이는 값이다.<MyStore.Provider value={this.state}>
<subComponent1 />
<subComponent2 />
</MyStore.Provider>
provider
는 정의한 context를 하위 컴포넌트에게 전달하는 역할을 한다.value
를 사용해야 한다.<MyContext.Consumer>
{value => /* context 값을 이용한 렌더링 */}
</MyContext.Consumer>
createContext()
에서 정의한 defaultValue
를 가지게 된다.// 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;
// 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>
);
}
}
//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: ""
};
//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>
</>
);
}
}
//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: {},
};