App 컴포넌트 아래에 Profile 컴포넌트 아래에 Greeting 컴포넌트가 있다고 가정을 해 봅시다.
export default function App() {
return (
<div>
<div>상단 메뉴</div>
<Profile username="horong" />
<div>하단 메뉴</div>
</div>
);
}
function Profile({ username }) {
return (
<Greeting username={username} />
{/* ... */}
);
}
function Greeting({ username }) {
return <p>{`${username}님 안녕하세요`}</p>;
}
App 컴포넌트의 상태값을 Greeting 컴포넌트가 사용하는 경우에는 아래와 같은 불필요한 코드를 작성하게 됩니다.
여기서는 Profile 컴포넌트 코드에서 Greeting 컴포넌트에 전달하는 코드 하나만 작성하면 됩니다.
function Profile({ username }) {
return (
<Greeting username={username} />
{/* ... */}
);
}
하지만 만약 ... 4 ~ 5 계층 아래의 컴포넌트에게 데이터를 전달하는 경우에는 어떻게 될까요? 그 사이에 있는 모든 컴포넌트에 전달하는 코드를 작성해 주어야 합니다. 이러한 코드를 없애기 위해 사용하는 것이 Context API 입니다.
위에서 작성한 예제 코드를 가지고 Context API를 사용한 방법으로 바꾸어 보겠습니다.
우선 상태값을 전달하는 컴포넌트인 App 컴포넌트를 Provider로 지정할 것입니다. 아래와 같이 상태값을 전달할 영역을 UserContext.Provider 요소로 묶어주고 value 속성에 값을 전달해 줍니다.
import React, { createContext } from 'react';
const UserContext = createContext('unknown');
export default function App() {
return (
<div>
<UserContext.Provider value="horong">
<div>상단 메뉴</div>
<Profile />
<div>하단 메뉴</div>
</UserContext.Provider>
</div>
);
}
Profile 컴포넌트는 이제 데이터를 전달할 필요가 없으므로 아래와 같이 속성값 전달 코드를 지워줍니다.
function Profile() {
return (
<Greeting />
{/* ... */}
);
}
이제 Greeting 컴포넌트를 Consumer 로 지정하여 위에서 지정한 Provider 가 제공하는 데이터를 가져옵니다. 아래에 작성한 코드는 컴포넌트 안에 함수를 작성하는 render props 형태로 작성한 코드입니다.
username 값은 위에서 정의한 Provider의 value 값이고 value 값이 없다면 컨택스트를 생성할 때 넣어준 unknown 이라는 초기값이 사용됩니다.
function Greeting() {
return (
<UserContext.Consumer>
{username => <p>{`${username}님 안녕하세요`}</p>}
</UserContext.Consumer>
)
}
그런데 여기서 username 값을 return 블록 외부에서는 사용할 수 없다는 단점이 있는데, 이 부분은 useContext 라는 리액트 훅을 사용하면 해결하실 수 있습니다. Consumer 컴포넌트의 코드를 리액트 훅을 사용하여 아래와 같이 수정하실 수 있습니다.
function Greeting() {
const username = useContext(UserContext);
return <p>{`${username}님 안녕하세요`}</p>
}
데이터의 종류별로 컨택스트를 나누면 성능상의 이점이 있습니다. 예를 들면, ThemeContext를 사용하는 부분은 UserContext의 데이터가 변경 되더라도 불필요하게 렌더링이 되지 않을 것입니다.
import React, { createContext } from 'react';
const ThemeContext = createContext('dark');
const UserContext = createContext('unknown');
export default function App() {
return (
<div>
<ThemeContext.Provider value="light">
<UserContext.Provider value="horong">
<div>상단 메뉴</div>
<Profile />
<div>하단 메뉴</div>
</UserContext.Provider>
<ThemeContext.Provider>
</div>
);
}
상태값을 수정하는 함수 자체를 별도의 컨텍스트로 만들어서 내려줄 수 있습니다. 아래의 코드를 보시면 setUser 라는 상태값 변경 함수를 SetUserContext 라는 컨텍스트 객체로 만들어서 하위 컴포넌트에서 사용할 수 있도록 내려주고 있습니다.
import React, { createContext } from 'react';
const UserContext = createContext({ username: 'unknown', helloCount: 0 });
const SetUserContext = createContext(() => {});
export default function App() {
const [user, setUser] = useState({ username: 'horong', helloCount: 0 });
return (
<div>
<SetUserContext.Provider value={setUser}>
<UserContext.Provider value={user}>
<div>상단 메뉴</div>
<Profile username="horong" />
<div>하단 메뉴</div>
</UserContext.Provider>
<SetUserContext.Provider>
</div>
);
}
하위 컴포넌트에서는 아래와 같이 useContext 훅을 사용하여 함수와 데이터를 받아와서 사용하실 수 있습니다.
import React, { useContext, createContext } from 'react';
export default function Greeting() {
const setUser = useContext(SetUserContext);
const { username, helloCount } = useContext(UserContext);
return (
<>
<p>{`${username}님 안녕하세요`}</p>
<p>{`인사 횟수: ${helloCount}`}</p>
<button onClick={() => setUser({ username, helloCount: helloCount + 1})}>
인사하기
</button>
</>
);
}
아래와 같이 Provider 의 value 값을 username, age 로 전달을 하는 경우에는 App 컴포넌트가 렌더링 될 때마다 매번 새로운 객체의 value 값이 만들어집니다. 따라서 Consumer에 해당하는 컴포넌트가 불필요하게 다시 렌더링이 됩니다.
import React, { createContext } from 'react';
const UserContext = createContext({ username: 'unknown', age: 0 });
export default function App() {
const [username, setUsername] = useState('');
const [age, setAge] = useState(0);
return (
<div>
<UserContext.Provider value={ username, age }>
<Profile />
</UserContext.Provider>
</div>
);
}
이러한 불필요한 렌더링을 막기 위해서는 value 값을 별도의 객체로 관리해야 합니다. 아래와 같이 value 에 들어갈 객체를 통째로 상태값으로 관리하게 되면, 상태 객체가 변경되지 않는 경우에는 하위의 컴포넌트가 불필요하게 리렌더링 되지 않습니다.
import React, { createContext } from 'react';
const UserContext = createContext({ username: 'unknown', age: 0 });
export default function App() {
const [user, setuser] = useState({ username: 'horong', age: 23 });
return (
<div>
<UserContext.Provider value={user}>
<Profile />
</UserContext.Provider>
</div>
);
}
출처 : 실전 리액트 프로그리맹 - 인프런 (추천합니다 !!)
[Frontend/React] 14. 리액트 내장 훅 살펴보기 (0) | 2020.10.03 |
---|---|
[Frontend/React] 13. 자식 요소에 접근하기 (0) | 2020.10.03 |
[Frontend/React] 11. 리액트 훅(hook) (0) | 2020.10.03 |
[Frontend/React] 10. 리액트 요소와 가상 돔 (0) | 2020.10.03 |
[Frontend/React] 9. Fragment / Potal 사용하기 (0) | 2020.10.03 |