위와 같이 App 컴포넌트에서 데이터를 컴포넌트에게 전달 해줄려면 prop 으로 하위 컴포넌트를 따라 전달해 줘야 합니다. 이것을 prop drilling 이라고 합니다.
보기만해도 피곤하며 prop 를 엉뚱한 컴포넌트에게 전달하거나 전달해주는것을 깜빡할것만 같습니다. 그리고 굳이 해당 데이터가 필요 없는 컴포넌트가 하위 컴포넌트가 데이터가 필요하다는 이유로 prop 로 데이터를 받아야 합니다. 너무 비효율적 입니다.
우리에게 희망적인 소식이 있습니다.
React 는 App 안에서 전역적으로 사용되는 데이터들을 컴포넌트들끼리 쉽게 공유할 수 있는 방법을 제공합니다. context 에 모든 데이터를 공유하면 데이터가 필요한 컴포넌트가 useContext 를 사용하면 하나하나 prop 를 이용해 컴포넌트에 전달해주지 않아도 하위 컴포넌트가 데이터를 받아 사용 할 수 있습니다.
하지만 Context 를 사용하면 컴포넌트를 재사용하기 어려워 질 수 있기 때문에 필요할때만 사용하고 일반적으로는 prop 를 사용하는게 컴포넌트의 재사용성을 높여 줍니다. Context 의 사용이 prop drilling 을 하지 않기 위한 목적으로 사용한다면 컴포넌트 합성을 사용하는것이 좋은 선택일수도 있습니다.
그럼 이제 useContext 를 사용해봅시다!
최대한 간단하게 설명하기 위해서 App 컴포넌트를 최상위 컴포넌트로 두고 한개의 하위 컴포넌트인 Greet 라는 하위 컴포넌트만 만들어서 설명하도록 하겠습니다.
이 앱은 클릭하기! 버튼을 클릭하면 인사가 바뀌는 앱 입니다.
위의 앱에서는 두개의 컴포넌트가 필요 합니다.
import React, { useState } from 'react';
import Greet from './Greet';
const App = () => {
const [greet, setGreet] = useState('안녕하세요!');
return (
<>
<Greet greet={greet} setGreet={setGreet} />
</>
);
};
export default App;
App 컴포넌트와
import React from 'react';
const Greet = ({ greet, setGreet }) => {
const onclickGreetButton = () => {
setGreet(greet === '안녕하세요!' ? '안녕히가세요!' : '안녕하세요!');
};
return (
<>
<div>
<h1>{greet}</h1>
<button onClick={onclickGreetButton}>인사하기!</button>
</div>
</>
);
};
export default Greet;
Greet 컴포넌트 입니다.
App 컴포넌트에서 Greet 컴포넌트에게 prop 로 greet, setGreet 를 전달 합니다.
그리고 prop 로 전달 받은 것을 Greet 컴포넌트에서 사용합니다.
제가 아주아주 간단한 앱을 만들었기 때문에 하나의 하위 컴포넌트에 prop 을 전달했지만 실제 우리가 사용하는 앱들은 아주아주 많은 하위 컴포넌트들이 존재하고 하위 컴포넌트에게 데이터를 전달하기 위해서 prop 을 전달해야 할 것 입니다.
이제 우리는 Context 를 사용해 prop 로 전달했던 데이터를 저장하고 그 데이터를 필요로 하는 컴포넌트가 useContext 로 데이터를 받아 사용하도록 코드를 작성 해 보겠습니다.
import { Context, createContext } from 'react';
export const GreetData = createContext(null);
먼저 새로운 파일 만들어 줍니다. 확장자는 js 또는 jsx 로 설정 합니다.
저는 GreetData 라는 이름의 파일을 만들었고 위와 같은 코드를 작성해 Context 를 생성했습니다.
import React, { useState } from 'react';
import Greet from './Greet';
import { GreetData } from './Greetdata';
const App = () => {
const [greet, setGreet] = useState('안녕하세요!');
return (
<>
<GreetData.Provider value={{ greet, setGreet }}>
<Greet />
</GreetData.Provider>
</>
);
};
export default App;
이제 App 컴포넌트 코드에서 위와 같이 변경 해 줍니다.
이전에 prop 를 사용할때와 변경 된 것은 GreetData.provider 라는 사용자 태그로 기존의 Greet 컴포넌트를 감싸주는 것과 Greet 컴포넌트로 이제 prop 를 전달하지 않는다는 것 그리고 prop 를 대신해서 GreetData.provider 사용자 태그에 value 라는 prop 을 받는데 값으로 모든 컴포넌트가 사용할 데이터를 객체 형태로 전달 합니다.
이렇게 하면 하위 컴포넌트가 필요한 데이터가 있다면 useContext 를 사용해서 데이터를 가져와 사용할 수 있습니다.
import React, { useContext } from 'react';
import { GreetData } from './Greetdata';
const Greet = () => {
const { greet, setGreet } = useContext(GreetData);
const onclickGreetButton = () => {
setGreet(greet === '안녕하세요!' ? '안녕히가세요!' : '안녕하세요!');
};
return (
<>
<div>
<h1>{greet}</h1>
<button onClick={onclickGreetButton}>인사하기!</button>
</div>
</>
);
};
export default Greet;
Greet 컴포넌트에서는 greet, setGreet 가 필요하기 때문에 useContext(생성한 context) 로 필요한 데이터를 가져와서 사용 합니다.
위의 작은 앱을 만드는 경우에는 사실 Context, useContext 를 사용하는것보다 그냥 prop 으로 데이터를 전달해주는 것이 효율적일 것 입니다. 하지만 많은 하위 컴포넌트를 가지는 앱에서는 Context, useContext 를 사용하면 효율적으로 데이터를 필요로 하는 하위 컴포넌트에게 데이터를 전달하도록 하여 앱을 만들 수 있을 것 입니다.