React에서 컴포넌트가 데이터를 다루는 방법으로 Props, State, Context가 있다.
리액트에서 props와 state는 부모 컴포넌트와 자식 컴포넌트 또는 한 컴포넌트 안에서 데이터를 다루기 위해 사용된다.
props와 state를 사용하게 되면, 부모 컴포넌트에서 자식 컴포넌트, 즉 위에서 아래, 한쪽으로 데이터가 흐르게 된다.
만약, 다른 컴포넌트에서 한쪽으로 흐르고 있는 데이터를 사용하고 싶은 경우 또는 다른 컴포넌트에서 사용하고 있는 데이터를 현재의 데이터 흐름에 넣고 싶은 경우가 발생한다면?
리액트에서 데이터는 위에서 아래로 흐르게 되므로 사용하고 싶은 데이터와 이 데이터를 사용할 컴포넌트의 공통 부모 컴포넌트에 state를 만들고, 사용하고자 하는 데이터를 props로 전달하면 이 문제를 해결할 수 있다.
근데 위처럼 컴포넌트 사이에 공유되는 데이터를 위해 매번 공통 부모 컴포넌트를 수정하고 하위 모든 컴포넌트에 데이터를 props로 전달하는 것은 매.우. 비효율적이다. 이를 위해 리액트는 flux 개념을 도입했고, 그에 맞는 Context API를 제공하기 시작했다.
Context는 부모 컴포넌트로부터 자식 컴포넌트로 전달되는 데이터의 흐름과는 상관없이 전역적인 데이터를 다룰 때 사용한다.
전역 데이터를 Context에 저장한 후, 데이터가 필요한 컴포넌트에서 해당 데이터를 불러와 사용할 수 있다.
리액트에서 Context를 사용하기 위해서는 Context API를 사용해야 하고, Context의 Provider와 Consumer를 사용해야 한다.
Context에 저장된 데이터를 사용하기 위해서는 공통 부모 컴포넌트에 Context의 Provider를 사용하여 데이터를 제공해야 하며, 데이터를 사용하려는 컴포넌트에서 Context의 Consumer를 사용하여 실제로 데이터를 사용한다.
Context 생성
import { createContext, useState } from 'react';
// 리액트에서 Context를 생성하기 위해서는 createContext를 사용
// 전역 변수로 사용될 데이터의 초기값을 설정
const CountContext = createContext({
count: 0,
plusCount: () => {},
});
interface Props {
children: JSX.Element | JSX.Element[];
}
// Context는 하나의 react 컴포넌트. Provider로 감싸기!
const CountProvider = ({ children }: Props): JSX.Element => {
// 내부적으로 변경 가능한 데이터를 사용하려면 useState로
const [count, setCount] = useState(0);
const plusCount = (): void => {
setCount(count + 1);
};
// plusCount로 만든 state를 Context의 Provider에 제공
return (
<CountContext.Provider
value={{
count,
plusCount,
}}>
{children}
</CountContext.Provider>
);
};
export { CountContext, CountProvider };
Provider
Context를 사용하기 위해, 공통 부모 컴포넌트인 App 컴포넌트에 Context의 Provider를 제공하자.
// App.tsx
import { CountProvider } from './Contexts/Count';
import { CountLabel } from './Components/CountLabel';
import { PlusButton } from './Components/PlusButton';
function App() {
return (
<CountProvider>
<CountLabel />
<PlusButton />
</CountProvider>
);
}
export default App;
Context를 사용하여 전역 데이터를 사용하려면 공통 부모 컴포넌트에 Context의 Provider를 사용해야 한다. (Context의 Provider로 감싼다)
CountProvider로 감싼 부분에서는 Context 내부에 생성한 전역 데이터를 자유롭게 접근할 수 있다 !!
Consumer
Context를 사용하여 전역 데이터를 사용할 컴포넌트 코드 예시이다.
// CountLabel/index.tsx
import { useContext } from 'react';
import { CountContext } from '../../Contexts/Count';
export const CountLabel = () => {
const { count } = useContext(CountContext);
return <div>{count}</div>;
};
// PlusButton/index.tsx
import { useContext } from 'react';
import { CountContext } from '../../Contexts/Count';
export const PlusButton = () => {
const { plusCount } = useContext(CountContext);
return <button onClick={plusCount}>+ 1</button>;
};
그렇다면 상태관리를 할 때 Context API만 사용하지 않고, 상태관리 라이브러리를 사용할까?
Context는 컴포넌트에 의존성을 주입할 수 있는 아주 효과적인 방법 중 하나이다. 하지만 부모 컴포넌트 쪽에 Context.Provider
컴포넌트를 선언하고 Context로 전달되는 값이 변경될 때 해당 Context를 사용하는 모든 자손 컴포넌트는 리랜더링 된다.
컴포넌트에서 Context 값의 일부만 바꾸는 동작을 실행하더라도, InputConsumer
, CountConsumer
모두 리랜더링이 일어난다. 결국 객체 형태로 Context를 관리하면서 Context를 소비하는 컴포넌트가 많아질 경우 불필요한 리랜더링이 많이 일어나 애플리케이션의 성능 문제가 생길 수 있다.
이 문제를 해결하기 위한 방법은 여러가지다.
제일 권장되는 방법은 1번이지만, 여러 Context를 만들어 Provider로 주입할 때 Provider Hell이라 불리는 중첩 Provider로 인한 가독성 문제가 생긴다. 또한 애플리케이션 규모나 구조에 따라 다르지만 캘린더는 일원화된 상태 스토어를 사용하는게 더 효과적일 것이라 생각한다.
오늘은 상태 관리 라이브러리 중 회사에서 사용 중인 zustand에 대해 알아보려고 한다.
스토어를 만들 때는 create 함수를 이용하여 상태와 그 상태를 변경하는 액션을 정의한다. 그러면 리액트 컴포넌트에서 사용할 수 있는 useStore 훅을 리턴한다.
import create from 'zustand';
// set 함수를 통해서만 상태를 변경할 수 있다
const useStore = create(set => ({
bears: 0,
increasePopulation: () => set(state => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 })
}));
컴포넌트에서 useStore 훅을 사용할 때는 스토어에서 상태를 어떤 형태로 꺼내올지 결정하는 셀렉터 함수를 전달해줘야 한다. 만약 셀렉터 함수를 전달하지 않으면 스토어 전체가 리턴된다.
// 상태를 꺼낸다
function BearCounter() {
const bears = useStore(state => state.bears);
return <h1>{bears} around here ...</h1>;
}
// 상태를 변경하는 액션을 꺼낸다
function Controls() {
const increasePopulation = useStore(state => state.increasePopulation);
return <button onClick={increasePopulation}>one up</button>;
}
상태관리 라이브러리가 다양한데, 이 다양한 라이브러리를 활용하는 사람이 있는 반면, 최대한 사용을 줄이려는 사람도 있는 거 같다. 뭐가 정답인지는 모르겠지만 모든 방법을 다 아는게 좋지 않을까..^^ 생각하는 마음으로 정리해봤다.
다음에 다른 상태관리로 돌아올게요~~ (급마무리)
https://deku.posstree.com/ko/react/context-api/
https://ui.toast.com/posts/ko_20210812