React- 컴포넌트 간 통신 , Context API

정지훈·2021년 3월 28일
0

컴포넌트 간 통신

컴포넌트를 많이 만들면 컴포넌트 안에서의 로직을 만드는 것도 중요하지만 컴포넌트 간에 통신하는 것도 골머리를 아프게 된다...

하위 컴포넌트를 변경하기

만약 A의 버튼을 클릭해서 B안에 있는 C안에 있는 D안에 있는 E의 value를 바꾸고 싶으면

  1. <A />컴포넌트에서 button에 onClick이벤트를 만들고,
  2. button을 클릭하면, <A />의 state를 변경하여 <B />로 내려주는 props를 변경
  3. <B />의 props가 변경되면 <C />의 props에 전달
  4. <C /> 의 props가 변경되면, <D />의 props로 전달
  5. <D /> 의 props가 변경되면, <E />의 props로 전달

이렇게 주고 받고 주고 받으니 골머리를 때린다.

상위 컴포넌트를 변경하기

E의 button을 클릭하여 A의 P를 변경하려면

  1. <A />에 함수를 만들고 그 함수 안에 state를 변경하도록 구현, 그 변경으로 인해 P안의 내용을 변경
  2. 만들어진 함수를 props에 넣어서 <B />로 전달.
  3. <B />의 props의 함수를 <C />의 props로 전달.
  4. <C />의 props의 함수를 <D />의 props로 전달.
  5. <D />의 props의 함수를 <E />의 props로 전달, <E />에서 클릭하면 props로 받은 함수를 실행
import React from "react";

class A extends React.Component {
	state = {
		value: "아직 안바뀜",
	};

	render() {
		// B에게 change props로 전달한다.
		console.log("A render");
		return (
			<div>
				<h3>{this.state.value}</h3>
				<B change={this.change} />
			</div>
		);
	}

	change = () => {
		this.setState({
			value: "A 의 값을 변경",
		});
	};
}

export default A;

const B = (props) => (
	<div>
		<p>여긴 B</p>
		<C {...props} />
	</div>
);

const C = (props) => (
	<div>
		<p>여긴 C</p>
		<D {...props} />
	</div>
);

const D = (props) => (
	<div>
		<p>여긴 D</p>
		<E {...props} />
	</div>
);

const E = (props) => {
	function click() {
		props.change(); // 받아서 실행을 한다.
	}
	return (
		<div>
			<p>여긴 E</p>
			<button onClick={click}>클릭</button>
		</div>
	);
};

E에서 받아서 실행하면 value값이 바뀌고 부모가 바뀌었으니 자식도 다시 바뀐다. 이게 계속 반복이 된다.

state는 최상단 부모한테 있어야지 컴포넌트 간에 커뮤닠이션이 활성화 된다.

관리하는데 너무 복잡해 진다. (찾기도 어렵다)

그래서 나온게 Context API이다. withRouter랑 비슷하다.

Context API

Context API를 사용하면 위 아래 관계에 없어도 점프할 수 있다. 그런 개념이다. 점프를 할 수 있는 기능을 준다는 것만 기억하자.

데이터를 조작하거나 있는 곳은 가장 부모에서 하는일이 모든 곳에 다 전파 되어야 하는 상황이다. 가장 부모와 해당 데이터를 변경하는 함수를 사용하는 아이가 저 멀리 어딘가 있고 저 멀리 어딘가에 있는 아이와 이 부모가 만든 메서드 데이터를 연결하는 행위가 힘들다. 그래서 Context API는 가장 상위에 데이터와 그 데이터를 바꾸는 아이를 두는 것이다.

그 아이를 다른 컴포넌트한테 주는 방법은 그 컨텍스트에서 가져와 하면 가져올 수 있다.

그래서 데이터를 Set하는 놈이 있어야 한다.

(Provider) Set하는 놈을 프로바이더라고 한다. (주는 놈)

데이터를 GET 하는 놈

  • 모든 하위 컴포넌트에서 접근 가능
  • 컨슈머로 하는 방법 (컨슈머는 소비자 받아서 쓰는 놈)
    - 클래스 컴포넌트의 this.context로 하는 방법
    • 펑셔널 컴포넌트의 useContext로 하는 방법

사용하기 위해서 create-react-app을 해서 새로운 프로젝트를 만들고 시작하자.

  • 1단계
    컴텍스트를 생성한다.

contexts폴더를 생성하고 그 안에다가 PersonContext.js를 만들자.

  • PersonContext.js
import React from "react";

// context를 생성하는 API를 생성하자.

const PersonsContext = React.createContext(); // 이게 context이다.

//공유하기 위해 내보내자.
export default PersonContext;

PersonContext.Provider를 사용한다는 것이다. 이것을 어디서 사용할 까?

데이터를 set하는 놈을 어디서 할까? 그건 가장 상위 컴포넌트에 하는 것이다. 그래야 하위 컴포넌트에서 받아서 사용할 수 있기 때문이다.

즉 router를 설정할 app.js나 최상위인 index.js에 해야한다.

index.js로 가서 Provider를 감싸서 value를 설정하자.

  • index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import PersonContext from "./contexts/PersonContext";

const persons = [
	{ id: 0, name: "Mark", age: 38 },
	{ id: 1, name: "Hanna", age: 27 },
];

ReactDOM.render(
	<React.StricMode>
		<PersonContext.Provider value={persons}>
			<App />
		</PersonContext.Provider>
	</React.StricMode>,
	document.getElementById("root")
);

이렇게 value를 넣자.

<PersonContext.Provider value={persons}> 이 부모 밑에 있는 아이들은 props를 안넣어도 value를 사용할 수 있다.

그러면 components폴더를 만들어서 Example1.jsx, Example2.jsx, Example3.jsx를 생성해 보자.

  • Example1.jsx
import React from "react";
import PersonContext from "../contexts/PersonContext";

// 데이터를 GET하기 - 컨슈머 사용하기 class function 관계 없음

export default function Example1() {
	return (
		// 이 안에 함수를 넣자.
		<PersonContext.Consumer>
			{(value) => <p>{JSON.stringify(value)}</p>}
		</PersonContext.Consumer>
	);
}

이렇게 만들고 App.js 에서 가서 만들자.

  • App.js
import Example1 from "./components/Example1";
import "./App.css";
function App() {
	return (
		<div className='App'>
			<header className='App-header'>
				<Example1 />
			</header>
		</div>
	);
}

이렇게 한다. 그러면 value에 대한 데이터가 화면에 나올 것이다.

P태그를 ul로 바꿔 보자.

  • Example1.jsx
import React from "react";
import PersonContext from "../contexts/PersonContext";

// 데이터를 GET하기 - 컨슈머 사용하기 class function 관계 없음

export default function Example1() {
	return (
		// 이 안에 함수를 넣자.
		<PersonContext.Consumer>
			{(value) => (
				<ul>
					{value.map((person) => (
						<li>{person.name}</li>
					))}
				</ul>
			)}
		</PersonContext.Consumer>
	);
}

하면 name이 화면에 나올 것이다. 이걸 스타일을 적용하면 된다.

이렇게도 사용을 한다. personContext를 직접 들고오지 않고 PersonContext.Consumer를 다른 이름으로 붙어서 걔를 들고 올수 있다.

PersonContext.js로 가서

import React from "react";

// context를 생성하는 API를 생성하자.

const PersonsContext = React.createContext(); // 이게 context이다.

//공유하기 위해 내보내자.
export default PersonContext;

export const { Provider, Consumer } = PersonContext;

PersonContext.Provider를 않쓰고 Provider 가져다가 Provider하면 됀다.

  • index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "./contexts/PersonContext";

const persons = [
	{ id: 0, name: "Mark", age: 38 },
	{ id: 1, name: "Hanna", age: 27 },
];

ReactDOM.render(
	<React.StricMode>
		<Provider value={persons}>
			<App />
		</Provider>
	</React.StricMode>,
	document.getElementById("root")
);

Example1.jsx

import React from "react";
import { Consumer } from "../contexts/PersonContext";

// 데이터를 GET하기 - 컨슈머 사용하기 class function 관계 없음

export default function Example1() {
	return (
		<>
			<h1>Consumer 사용</h1>
			<Consumer>
				{/* 이 안에 함수를 넣자.*/}
				{(value) => (
					<ul>
						{value.map((person) => (
							<li>{person.name}</li>
						))}
					</ul>
				)}
			</Consumer>
		</>
	);
}

이런 식으로 사용할 수 있다.

보통 Consumer를 hoc나 훅으로 사용할 수 있다.

2번째 장법 class로 사용해서 get하는 방법

Example2.jsx로 가서 만들어 보자.
this.context를 사용해 보자,

Example2.jsx

class Example2 extends React.Component {
	render() {
		<>
			<h1>this.context 사용</h1>
			<ul>
				{this.context.map((person) => (
					<li>{person.name}</li>
				))}
			</ul>
		</>;
	}
}

export default Example2;

App.js에서 2를 추가하자.

import Example1 from "./components/Example1";
import Example2 from "./components/Example2";

import "./App.css";
function App() {
	return (
		<div className='App'>
			<header className='App-header'>
				<Example1 />
				<Example2 />
			</header>
		</div>
	);
}

이러면 Error가 발생할 것이다. this.context가 지정이 안되어 있어서 이다.
그래서 규명해 주는 방법인 첫번째가

class Example2 extends React.Component {
	render() {
		<>
			<h1>this.context 사용</h1>
			<ul>
				{this.context.map((person) => (
					<li>{person.name}</li>
				))}
			</ul>
		</>;
	}
	static contextType = PersonContext; // 이렇게 규명해 준다.
}

export default Example2;

위 에서 문법적으로는 아래랑 똑같다.

class Example2 extends React.Component {
	render() {
		<>
			<h1>this.context 사용</h1>
			<ul>
				{this.context.map((person) => (
					<li>{person.name}</li>
				))}
			</ul>
		</>;
	}
	// static contextType = PersonContext; // 이렇게 규명해 준다.
}

Example2.contextType = PersonContext;
// => 이렇게 쓰면 function 에서도 쓸 수 있다.

export default Example2;

이렇게 이야기 한거는 무슨 의미냐? class에서만 사용할 수 있는 방법이 아니고 class는
static contextType에 컨텍스트를 설정한다., this.context => value이다 이렇게만 쓸 수 밖에 없다.
왜냐면 1번으로도 쓸 수 있지만 결정적으로 3번째 방법은 사용할 수 없다.

Example2.contextType = PersonContext; 이렇게 쓴다는 것은 무엇이냐면 Example2에다가 contextType을 setting 한것이다. PersonContext를 set한것이다.

간과해선 안될 부분은 index.js에 Provider가 여러개일 수도 있다.(컨텍스트가 여러개 일 수도 있다.) 그 컨텍스트 중에서 Example2.contextType = PersonContext;얘는 클래스에서 하나만 쓴다는 것이다. 이렇게 쓸 필요가 없다. 함수 컨텍스트에서는 useContext를 쓰면 된다.

Exampe3번으로 가보자.
Example3은 hocks를 쓰는 방법이다. (useContext())

import React from 'react';
import PersonContext from "../contexts/PersonContext";

export default function Example3() {
	const persons = useContext(PersonContext);
	return (
				<>
			<h1>useContext 사용</h1>
			<ul>
				{persons.map((person) => (
					<li>{person.name}</li>
				))}
			</ul>
		</>;
	)
}

즉 이 방법은 다른 context를 막 쓰더라도 상관이 없다. 이 방법이 좋은 방법이라 생각이 든다.
만약 persons에다 데이터를 추가하는 버튼을 만드려면 어떻게 해야할까? 어떤 데이터를 추가 삭제 해더라도 새로 랜더를 해야한다면 프롭아니면 스테이트다.
즉 이걸 어떻게 바꿔야 하나?

import React from 'react';
import PersonContext from "../contexts/PersonContext";

export default function Example3() {
	const persons = useContext(PersonContext);
	return (
				<>
			<h1>useContext 사용</h1>
			<ul>
				{persons.map((person) => (
					<li>{person.name}</li>
				))}
			</ul>
			<button onClick={click}>추가</button>
		</button>;
		</>
	);
	function click() {

	}
}

index.js에서 value={이것을 객체로 넣자}

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "./contexts/PersonContext";

const persons = [
	{ id: 0, name: "Mark", age: 38 },
	{ id: 1, name: "Hanna", age: 27 },
];

const add = () => {}; // 임시로 만든것

ReactDOM.render(
	<React.StricMode>
		<Provider value={{ persons, add }}>
			<App />
		</Provider>
	</React.StricMode>,
	document.getElementById("root")
);

이렇게 바꾸고 Example1,2,3도 바꿔야한다.

Example1.jsx

import React from "react";
import { Consumer } from "../contexts/PersonContext";

// 데이터를 GET하기 - 컨슈머 사용하기 class function 관계 없음

export default function Example1() {
	return (
		<>
			<h1>Consumer 사용</h1>
			<Consumer>
				{/* 이 안에 함수를 넣자.*/}
				{({ persons }) => (
					<ul>
						{persons.map((person) => (
							<li>{person.name}</li>
						))}
					</ul>
				)}
			</Consumer>
		</>
	);
}

Example2.jsx

class Example2 extends React.Component {
	render() {
		const { persons } = this.context;
		<>
			<h1>this.context 사용</h1>
			<ul>
				{persons.map((person) => (
					<li>{person.name}</li>
				))}
			</ul>
		</>;
	}
	// static contextType = PersonContext; // 이렇게 규명해 준다.
}

Example2.contextType = PersonContext;
// => 이렇게 쓰면 function 에서도 쓸 수 있다.

export default Example2;

Example3.jsx

import React from 'react';
import PersonContext from "../contexts/PersonContext";

export default function Example3() {
	const {persons, add} = useContext(PersonContext);
	return (
				<>
			<h1>useContext 사용</h1>
			<ul>
				{persons.map((person) => (
					<li>{person.name}</li>
				))}
			</ul>
			<button onClick={click}>추가</button>
		</button>;
		</>
	);
	function click() {
		add();
	}
}

이렇게 변경을 하자. 이제 버튼을 클릭을 하면 add를 실행하자.
눌려도 아무 일도 일어나지 않는다.

다시 index.js로 가서

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "./contexts/PersonContext";

const persons = [
	{ id: 0, name: "Mark", age: 38 },
	{ id: 1, name: "Hanna", age: 27 },
];

const add = () => {}; // add를 누르면 persons의 데이터가 바뀌면서 render가 다시 되도록 해야한다. 그래서 Provider를 App.js로 보내야한다.

ReactDOM.render(
	<React.StricMode>
		<Provider value={{ persons, add }}>
			<App />
		</Provider>
	</React.StricMode>,
	document.getElementById("root")
);

add를 누르면 persons의 데이터가 바뀌면서 render가 다시 되도록 해야한다. 이런식으로 하면 랜더를 다시 못하니 그래서 Provider를 App.js로 보내야한다.

index.js

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";

ReactDOM.render(
	<React.StricMode>
		<App />
	</React.StricMode>,
	document.getElementById("root")
);

이렇게 프로바이더를 빼고 App.js로 가서 붙여준다.

import Example1 from "./components/Example1";
import Example2 from "./components/Example2";
import Example3 from "./components/Example3";
import { Provider } from "./contexts/PersonContext";
import "./App.css";

const persons = [
	{ id: 0, name: "Mark", age: 38 },
	{ id: 1, name: "Hanna", age: 27 },
];

const add = () => {}; // add를 누르면 persons의 데이터가 바뀌면서 render가 다시 되도록 해야한다. 그래서 Provider를 App.js로 보내야한다.

function App() {
	return (
		<Provider value={{ persons, add }}>
			<div className='App'>
				<header className='App-header'>
					<Example1 />
					<Example2 />
					<Example3 />
				</header>
			</div>
		</Provider>
	);
}

이렇게 하면 일단 나오기만 할 것이다. 그 다음에

import Example1 from "./components/Example1";
import Example2 from "./components/Example2";
import Example3 from "./components/Example3";
import { Provider } from "./contexts/PersonContext";
import "./App.css";

function App() {
	const [persons, setPersons] = useState([
		{ id: 0, name: "Mark", age: 38 },
		{ id: 1, name: "Hanna", age: 27 },
	]);
	function add() {
		setPersons([...persons, { id: 2, name: "React", age: 10 }]);
	} // 이렇게 하면 버튼을 누르면 person data가 바뀌면서 Provider가 다시 되면서 랜더가 다시 된다.

	return (
		<Provider value={{ persons, add }}>
			<div className='App'>
				<header className='App-header'>
					<Example1 />
					<Example2 />
					<Example3 />
				</header>
			</div>
		</Provider>
	);
}

이렇게 훅을 사용하면 된다.

Context API를 가지고 상태관리를 할 수 있지만 상위 컴포넌트에 store란 개념을 가진 redux를 가지고 상태관리 할 수 있다.

다음 블로그는 redux에 대해서 공부해 보자.

0개의 댓글