프로젝트를 진행하던 중 전역 상태 관리가 필요하다고 판단되어 Context API를 사용하게 되었다!
전역 상태 관리 제대로 써보는 건 첨인 나에게 너무 어려웠던 순간들이다,,
프젝 1차적인 개발이 모두 끝난 이 시점에서 Context API에 대해서 정리해보려고 한다.
컴포넌트 사이에 공유되는 데이터를 매번 부모 컴포넌트를 수정하고 하위 모든 컴포넌트에 데이터를 props로 전달하는 것은 매우 비효율적이고 컴포넌트들이 많아지면서 props drilling문제가 생길 수 있다. 그래서 React는 Flux라는 개념을 도입하면서 Context API가 함께 제공되기 시작했다.
(Flux에 대한 설명은 지난 포스팅을 참고해주세요❗️)
Context API는 리액트에 내장된 기능으로 props
를 사용하지 않아도 특정 값이 필요한 컴포넌트끼리 쉽게 값을 공유할 수 있게 해준다. 그래서 전역 상태 관리할 때 많이 사용하는 것이다!
Context API의 장점이라고 하자면 이미 리액트에 내장된 기능이기에 따로 패키지들을 설치하지 않아도 된다는 점이 편했던 것 같다.
Context API를 사용하기 위해서 숙지하고 있어야 할 필수 메서드에 대해서 먼저 알아보자.
const MyContext = React.createContext(defaultValue)'
context객체를 생성하는 과정이다. context객체를 구독하고 있는 컴포넌트를 렌더링할 때 React는 트리 상위에서 가장 가까이 있는 짝이 맞는 Provider로부터 현재값을 읽는다.
(defaultValue)
이 부분의 경우 Provider를 찾지 못했을 경우 쓰일 값을 설정해주는 것이다. 흔히 말하는 초기화를 생각하면 된다.
<MyContext.Provider value={}>
Provider는 context를 구독하는 컴포넌트들에게 context의 변화를 알리는 역할을 한다.
value
에 들어가는 props를 받아서 이 값을 하위 컴포넌트들에게 전달하는 것이다.
저기에 props가 있어야 하위 컴포넌트에서도 해당 값을 가져다가 사용할 수 있다.
Provider 하위에서 context를 구독하는 모든 컴포넌트는 Provider의 value가 바뀔 때마다 다시 렌더링된다.
<MyContext.Consumer>
{value => context값을 이용한 렌더링}
</MyContext.Consumer>
context의 변화를 구독하는 React컴포넌트이다. 이를 사용하면 함수형 컴포넌트 안에서 context를 구독할 수 있다.
주의
Consumer의 자식은 꼭 함수여야 한다!
이 함수는 context의 현재값을 받고 ReactNode를 반환한다. 이 함수가 받는 value 매개변수 값은 해당 context의 Provider 중 상위 트리에서 가장 가까운 Providerd의 value prop과 동일하고 상위에 Provider가 없다면 value매개변수의 값은 createContext에서 보낸 defaultValue와 동일할 것이다.
열심히 context를 만들었다면 이제는 하위의 컴포넌트들에서 이 Context를 불러와 사용하면 된다.
const value = useContext(MyContext);
context객체(createContext에서 반환된 값)을 받아 그 context에 현재 값을 반환한다.
useContext훅을 사용하면 context객체 그 자체를 받는다는 것을 기억하자!
import React, {
createContext,
ReactNode,
useContext,
SetStateAction,
useState,
} from 'react';
interface Info {
name: string;
phoneNum?: string;
checkList: string[];
}
Info인터페이스를 정의해 필요한 정보를 나타낸다.
interface ContextProps {
info: Info;
setInfo: React.Dispatch<SetStateAction<Info>>;
}
ContextProps
는 Context에서 사용할 값과 상태를 정의하는 인터페이스이다.
info
는 정보를 나타내고 setInfo
는 이 정보들을 업데이트하는 함수이다.
const MyContext = createContext<MyContextProps | undefined>(undefined);
createContext
를 활용해 context를 생성하고 초기값은 undefined로 설정한다.
export const MyProvider: React.FC<ProviderProps> = ({ children }) => {
const [info, setInfo] = useState<Info>({
name: '',
phoneNum: '',
checkList: [],
});
return (
<MyContext.Provider
value={{
info,
setInfo
}}
>
{children}
</MyContext.Provider>
);
};
MyProvider
는 MyContext.Provider
를 활용해 컴포넌트에 대한 Context를 제공하며 초기 상태는 빈 값으로 설정한다.
export const useMyContext = () => {
const context = useContext(MyContext);
if (!context) {
throw new Error('useUser must be used within a UserProvider');
}
return context;
};
useMyContext
라는 커스텀 훅을 만든 것이다. 위에서 만든 MyContext를 useContext훅을 사용해 값을 추출하고 값이 없다면 에러를, 값이 있다면 해당 값을 리턴해준다.
예를 들어 이름, 나이, 직업 이렇게 세 가지에 대한 상태를 전역으로 관리해야 한다고 할 때, 나는 이 세 가지 모두 name, setName, age, setAge, job, setJob 이런식으로 모두 각각 관리해야 한다고 생각해서 context를 작성하는 과정에서 이렇게 할거면 그냥 각각을 useState를 써서 관리하는 거랑 뭐가 다른지에 대해서 고민을 많이 했다.
하지만 너무 단편적으로만 생각해서 발생한 고민이었다.
하나의 state내에서도 배열을 만들어서 각각을 관리할 수 있듯이 context도 마찬가지다!
위에서 만들었던 context를 하위 컴포넌트에서 가져가서 사용한다고 생각해보자
// 커스텀 훅을 그대로 불러와서 사용
const {info, setInfo} = useMyContext();
// 이렇게 info를 가져오면 info에는 {name: '', phoneNum: '', checkList: []}
// 이런식으로 값들이 존재하는 것이다.
// 여기서 값들을 바꾸고 싶다면 아래처럼 해주면 된다
setInfo({
name: 'sso',
phoneNum: '48484848',
checkList: ['aa', 'bb']
});
전역 상태 관리라는 말에 너무 겁을 먹어서 쉬운 것도 어렵게 생각했던 것 같다.
기존에 useState를 사용했던 것처럼 context를 만드는 것에만 익숙해진다면 이를 가져와서 활용하는 것은 어렵지 않게 사용할 수 있다!
하지만 아직 깊게 다루는 연습은 많이 부족하다 보니 더 많은 연습과 공부가 필요할 것 같다🥲
그럼 오늘 포스팅 끝!