-일반적으로 React는 단방향 바인딩으로 부모 컴포넌트에서 자식 컴포넌트로 props(자식요소)를 전달한다. 그러나 컴포넌트를 분리하다보면 컴포넌트 안에 컴포넌트를 여러개 해야하는 경우가 발생하고 이럴때, props-drilling
의 문제가 나타난다. 자식 컴포넌트에 props
를 계속 전달하면 코드가 지저분해지며, 무리한 리랜더링 문제가 발생할 수 있다. 이러한 경우에 대비해서 전역상태 관리의 필요성이 나타나며 전역상태 라이브러리들이 출시되었다.
물론, 양방향 바인딩인 vue.js의 경우에도 vuex인 store인 라이브러리들이 존재한다. 따라서, 양방향이든 단방향이든 문제라기보다는 상태 변화를 효과적이고 적절하게 필요한 컴포넌트에서 사용하는 것이 중요하다.
React에서 대표적인 전역상태관리로 context
, redux
와 가장 최근에 출시된 recoil
이 있다. 여기에서는 useContext
와 Recoil
에 대해서만 다루겠다.
useContext
로 사용하는 React의 훅이다. createContext
함수를 사용하여 context 객체를 생성하고 Provider
컴포넌트를 통해 context값을 하위 컴포넌트로 전달할 수 있다. seContext
훅 을 사용하여 해당 컨텍스트의 값을 받아와서 사용할 수 있습니다.
Recoil
은 Facebook에서 개발한 상태 관리 라이브러리입니다. useState
와 비슷한 문법을 사용하면서도 전역 상태 관리를 할 수 있습니다. Atom
이라는 단위로 상태를 정의하고, selector
를 사용하여 상태 값을 가공하거나 다른 Atom
의 값을 사용할 수 있습니다.
아마 대표적으로는 로그인과 같은 인증상태관리 또는 테마에 대해서 전역상태관리가 필요할 것이다. 이때, 개인적인 생각으로는 Recoil이 더 적합(?)한 것같다. 왜냐하면 Recoil은 상태 변경 시 변경된 상태에 대한 업데이트가 발생하면 자동으로 리랜더링이 된다.
이는 useState와 같은 다른 상태 관리 라이브러리와 비교했을 때도 마찬가지이지만, useContext
와 같은 Context API
를 사용한 상태 관리보다 더욱 효율적인 렌더링을 가능하게 한다.
이는 Recoil이 내부적으로 상태 값의 의존성 그래프(dependency graph)를 추적하고, 해당 그래프에서 변경된 부분만 리렌더링하도록 최적화하기 때문이다.
이를 통해, 관련 없는 부분까지 리렌더링하는 것을 방지하고, 불필요한 성능 저하를 막을 수 있습니다.
또한, Recoil
은 성능 최적화를 위해 Suspense와 같은 React의 다른 기능들과도 연동이 가능합니다. 이를 통해, 데이터가 로드되지 않은 상태에서 렌더링하는 것을 방지하고, 로딩 중인 상태를 처리할 수 있다.
따라서, Recoil은 내부적으로 상태 값의 의존성 그래프를 추적하고, 이를 최적화함으로써 더욱 효율적인 렌더링을 가능하게 합니다. 이러한 특징은 useContext와 같은 다른 상태 관리 방법보다 Recoil을 더욱 성능적으로 효율적인 상태 관리 방법으로 만든다.
Context와 Recoil은 React에서 상태 관리를 위한 라이브러리이며, 둘 다 컴포넌트 간에 상태를 전달하고 공유하는 방법을 제공한다.
useContext
와 Recoil
은 전역 상태 관리이지만 방식에서 차이가 있다. 만약 간단한 상태 값의 전달이 필요하다면 useContext
를 사용하는 것이 좋고, 전역 상태를 관리하거나 상태 값이 복잡한 경우에는 Recoil
을 사용하는 것이 좋다.
import { atom } from 'recoil';
export const isLoggedInState = atom({
key: 'isLoggedIn',
default: false,
});
위에서 정의한 전역 상태를 컴포넌트에서 사용하여 로그인 상태를 관리할 수 있습니다.
import React from 'react';
import { useRecoilState } from 'recoil';
import { isLoggedInState } from './recoil/atoms';
function LoginButton() {
const [isLoggedIn, setIsLoggedIn] = useRecoilState(isLoggedInState);
function handleLogin() {
setIsLoggedIn(true);
}
function handleLogout() {
setIsLoggedIn(false);
}
return (
<>
{isLoggedIn ? (
<button onClick={handleLogout}>로그아웃</button>
) : (
<button onClick={handleLogin}>로그인</button>
)}
</>
);
}
import React, { useContext, useState } from "react";
// createContext 함수로 컨텍스트 생성
const CounterContext = React.createContext();
function CounterProvider(props) {
const [count, setCount] = useState(0);
// 상태와 상태변경 함수를 value에 포함시켜 Provider 반환
return (
<CounterContext.Provider value={{ count, setCount }}>
{props.children}
</CounterContext.Provider>
);
}
function Counter() {
// useContext를 이용해 컨텍스트에서 상태와 상태변경 함수를 가져옴
const { count, setCount } = useContext(CounterContext);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
);
}
function App() {
return (
<CounterProvider>
<Counter />
</CounterProvider>
);
}
export default App;
createContext
로 CounterContext
라는 컨텍스트를 생성하고, CounterProvider
함수에서 상태와 상태변경 함수를 관리합니다. CounterProvider
함수는 createContext
를 이용해 생성한 CounterContext.Provider
를 반환하며, 이를 이용해 Counter 컴포넌트에서 useContext를 통해 상태와 상태변경 함수를 가져와 사용합니다. 상태변경 함수를 호출할 때마다 Counter 컴포넌트가 리렌더링됩니다.
Context
는 단순한 상태 전달이나 트리 상태 전달을 위한 유용한 패턴을 제공하며, 작은 규모의 애플리케이션에서는 적합합니다. Recoil
은 대규모 애플리케이션의 복잡한 상태 관리를 위해 사용할 수 있습니다. 상황에 따라 두 가지 방법을 적절하게 조합하여 사용할 수도 있습니다.