React에서 Props와 State는 부모 컴포넌트와 자식 컴포넌트 또는 한 컴포넌트 안에서 데이터를 다루기 위해 사용된다.
이 Props 와 State를 사용하게 되면 부모 컴포넌트에서 자식 컴포넌트 → 즉, 위에서 아래, 한쪽으로 데이터가 흐르게 된다.
만약 다른 컴포넌트에서 한쪽으로 흐르고 있는 데이터를 사용하고 싶은 경우 또는 다른 컴포넌트에서 사용하고 있는 데이터를 현재의 데이터 흐름에 넣고 싶은 경우가 발생한다면 어떻게 해야할까?
React에서 데이터는 위에서 아래로 흐르게 되므로 사용하고 싶은 데이터와 이 데이터를 사용할 컴포넌트의 공통 부모 컴포넌트에 State를 만들고, 사용하고자 하는 데이터를 Props를 전달하면 이 문제를 해결할 수 있다.
하지만 이처럼 컴포넌트 사이에 공유되는 데이터를 위해 매번 공통 부모 컴포넌트를 수정하고 하위 모든 컴포넌트에 Props로 전달하는 것은 매우 비효율적이다. (컴포넌트 기능으로서 작동하는 것이 아닌 중간 다리역할이 늘어난다.)
이와 같은 문제를 해결하기 위해 React의 Context API를 이용하여 해결할 수 있다.
React에서 Context를 사용하기 위해서는 Context API를 사용해야 하며, Context의 Provider와 Consumer를 사용해야 한다.
Context에 저장된 데이터를 사용하기 위해서는 공통 부모 컴포넌트에 Context의 Provider를 사용하여 데이터를 제공해야 하며, 데이터를 사용하려는 컴포넌트에서 Context의 Consumer를 사용하여 실제로 데이터를 사용한다.
**
Context API: 프로젝트 안에서 전역적으로 사용할 수 있는 값을 관리한다.
컴포넌트에게 함수를 전달해줘야 하는 상황에서 코드의 구조가 훨씬 깔끔해진다.
Context 사용법
생성하기
전달해줄 값을 createContext
를 통해 밖에서 생성해준다.
이 때, createContext
의 파라미터에는 Context 의 기본값을 설정할 수 있다.
const MyContext = createContext('defaultValue');
전달/사용하기
useContext
를 이용하여 createContext
로 생성한 전역 데이터값을 조회하여 사용할 수 있다.
전달해줄 값을 따로 설정하지 않았을 때는 createContext
에서 파라미터에 적어두었던 기본값이 전달된다.
아래 결과는 안녕하세요? defaultValue 로 출력된다.
function Child () {
const text = useContext(MyContext);
return <div>안녕하세요? {text}</div>;
};
전달/사용하기(특정값)
전달해주는 값을 Provider
라는 컴포넌트를 통해 value
에 전달해줄 값을 지정해준다.
이 때 Provider는 공통 부모 컴포넌트에 사용하여 데이터를 제공할 수 있도록 해준다.
그럼 위에 Child( )
함수에서 return 되는 결과는 안녕하세요? Good 이 된다.
이렇게 설정해주고 나면 Provider 에 의하여 감싸진 컴포넌트 중 어디서든지 Context 의 값을 다른 곳에서 조회해서 사용할 수 있다.
function ContextSample() {
return (
<MyContext.Provider value="Good">
<GrandParent />
</MyContext.Provider>
)
}
내보내기
내보내주고나면 나중에 사용하고 싶을 때 사용하고 싶은 곳에서 불러와서 사용할 수 있다.
export const UserDispatch = React.createContext(null);
예시
// context API를 사용하지 않았을 때 상위 컴포넌트에서 하위 컴포넌트로 전달하는 과정
function Child({ text }) {
return <div>안녕하세요? {text}</div>;
}
function Parent({ text }) {
return <Child text={text} />;
}
function GrandParent({ text }) {
return <Parent text={text} />;
}
function ContextSample() {
return <GrandParent text="Good" />;
}
export default ContextSample;
import React, { createContext, useContext } from 'react';
const MyContext = createContext('defaultValue'); // context 기본값을 'deafultValue'로 설정하여 생성하였다.
function Child() {
const text = useContext(MyContext);
return <div>안녕하세요? {text}</div>
};
function Parent() {
return <Child />
}
function GrandParent() {
return <Parent />
}
function ContextSample() {
return (
<MyContext.Provider value="GOOD">
<GrandParent text="GOOD" />
</MyContext.Provider>
);
}
export default ContextSample;
활용
useState 로 유동적인 값 만들기
function ContextSample() {
const [value, setValue] = useState(true);
return (
<MyContext.Provider value={value ? "GOOD" : 'BAD'}>
<GrandParent text="GOOD" />
<button onClick={() => setValue(!value)}>CLICK</button>
</MyContext.Provider>
);
}
useReducer 로 dispatch Context 만들기
UserDispatch 라는 Context 를 만들어서 어디서든지 dispatch 를 꺼내 쓸 수 있도록 만들어 준다!
// UserDispatch 라는 이름으로 내보내준다.
export const UserDispatch = React.createContext(null);
.
.
.
// Provider 컴포넌트의 value값으로 dispatch를 넘겨준다.
const count = useMemo(() => countActiveUsers(users), [users]);
return (
<UserDispatch.Provider value={dispatch}>
<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} onToggle={onToggle} onRemove={onRemove} />
<div>활성사용자 수 : {count}</div>
</UserDispatch.Provider>
);
}
UserList 에서 중간다리 역할로 App.js에서 구현하여 넘겨주었던 onRemove, onToggle을 필요한 컴포넌트에서 바로 사용하도록 연결해보자.
UserList.js
import React, { useContext } from 'react';
import { UserDispatch } from './App';
// UserDispatch 를 App 컴포넌트로 부터 불러와서 사용
const User = React.memo(function User({ user }) {
const dispatch = useContext(UserDispatch);
return (
<div>
<b
style={{
cursor: 'pointer',
color: user.active ? 'green' : 'black'
}}
onClick={() => {
dispatch({ type: 'TOGGLE_USER', id: user.id });
}}
>
{user.username}
</b>
<span>({user.email})</span>
<button
onClick={() => {
dispatch({ type: 'REMOVE_USER', id: user.id });
}}
>
삭제
</button>
</div>
);
});
function UserList({ users }) {
return (
<div>
{users.map(user => (
<User user={user} key={user.id} />
))}
</div>
);
}
export default React.memo(UserList);
useState vs. useReducer 차이점
useState만을 이용해서 내부에서 작업했다면 dispatch가 없기 때문에 컴포넌트를 원하는 곳에서 관리하는 것이 어려움.
물론, Provider 컴포넌트에서 value에 set함수를 넘겨주는 방식으로도 구현이 가능하지만, useReducer의 dispatch Context를 이용한 것처럼 깔끔하게 이루어지지 않을 것이다.
이처럼 특정 함수를 여러 컴포넌트에 거쳐서 전달해주어야 하는 값이 있다면 Dispatch를 관리하는 Context를 만들어서 필요한 곳에서 바로 dispatch를 불러와서 사용하면 구조도 깔끔해지고 편리할 것이다.
출처/참고