서비스의 규모가 커질수록 관리해야할 state가 많아지고 이를 체계적으로 관리하기 위해 상태 관리라는 라이브러리를 사용합니다.
그렇다면 이 상태 관리 라이브러리란 무엇일까요?
상태 관리 라이브러리란 말 그대로 state를 관리를 목적으로 하는 라이브러리입니다.
상태 관리 라이브러리의 대표적인 예로서는 리덕스라는 라이브러리가 있습니다.
리덕스는 모든 state를 store라는 중앙 상태 저장소를 사용하여 앱의 모든 상태를 저장합니다. 이 Store는 Reducer 함수를 통해 수정될 수 있습니다. 또한 이러한 Reducer 함수는 이전 상태와 액션 객체를 dispatch로 입력으로 받아 새로운 상태를 반환합니다.
dispatch(action) => action에 해당하는 리듀서 함수 실행 => store에 저장된 state 값 변경
컴포넌트에서는 상태값 자체를 변경시키는 것이 아닌 항상 특정 액션만을 dispatch 해줍니다. 이는 컴포넌트가 순수 함수 원칙을 유지할 수 있도록 도와줍니다.
그렇다면 이러한 상태 관리 라이브러리의 장점은 무엇일까요?
상태 관리 라이브러리를 사용함으로써 props drilling을 방지할 수 있습니다.
props drilling이라는 컴포넌트가 특정값을 사용하기 위해 여러 상위 컴포넌트에서 props를 정의하고 전달받는 과정을 말합니다.
이는 여러 컴포넌트에서 사용하지도 않는 props를 정의해야하고 필요없는 코드를 증가시킬 수 있습니다.
때문에 이러한 필요없는 코드를 줄이고 해당 데이터를 사용하는 컴포넌트에서만 끌어와서 쓸 수 있도록 할 수 있는 역할이 상태 관리 라이브러리입니다.
상태 관리 라이브러리에서는 store라는 state 저장소에서 사용하고 싶은 state만 끌어와 사용할 수 있기 때문에 props drilling을 피할 수 있습니다.
컴포넌트를 순수 함수로 유지할 수 있습니다.
리액트의 컴포넌트는 순수 함수로 유지되도록 하기를 권장합니다.
하지만 다음과 같은 상황에서 컴포넌트는 순수 함수를 유지할 수가 없습니다.
때문에 위와 같은 컴포넌트들은 상태 관리 라이브러리를 사용하여 순수 함수 컴포넌트로 만들어줄 수 있습니다.
외부 데이터를 API로 가져와 사용하는 컴포넌트는 외부 데이터에 의존하기 때문에 데이터의 변경에 따라 다른 값을 반환할 수 있습니다.
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { fetchPosts } from "./actions";
function PostList() {
const posts = useSelector((state) => state.posts);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchPosts());
}, [dispatch]);
return (
<div>
<h1>Post List</h1>
{posts.map((post) => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
))}
</div>
);
}
export default PostList;
side effect를 redux의 store 위임함으로써 컴포넌트 자체는 항상 특정 액션만 dispatch함으로 컴포넌트를 순수 함수로 유지할 수 있습니다.
브라우저 이벤트는 예측할 수 없으며, 이벤트에 대한 처리는 언제나 다른 결과를 가져올 수 있습니다. 따라서 브라우저 이벤트에 의존하는 컴포넌트는 순수 함수 원칙을 어기게 됩니다.
이를 상태 관리 라이브러리의 store에 분리시켜 컴포넌트는 순수 함수로 유지하도록 하게 할 수 있습니다.
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { incrementCount } from "../actions/counterActions";
const Counter = () => {
const dispatch = useDispatch();
const count = useSelector((state) => state.counter);
const handleClick = () => {
dispatch(incrementCount());
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
};
export default Counter;
이 또한 이벤트로 인해 발생한 side effect들을 store로 위임하여 컴포넌트 자체는 순수 함수로 유지할 수 있습니다.
Redux는 앱의 상태를 중앙에서 관리하기 때문에 디버깅이 쉽습니다. Redux DevTools와 같은 도구를 사용하면 앱의 상태 변화를 쉽게 추적할 수 있습니다.
따라서 상태 관리 라이브러리는 React와 함께 사용하면 상태 관리를 더욱 예측 가능하고 일관된 방식으로 관리할 수 있으며, 코드의 가독성을 높이고 디버깅을 쉽게 할 수 있도록 도와줍니다.
하지만 상태 관리 라이브러리를 사용한다고 해서 모든 상태를 꼭 store에 저장할 필요는 없습니다.
특정 컴포넌트 자체에서만 사용하는 state가 있다면 굳이 해당 state를 store로 끌어올 필요는 없습니다.
각 state의 역할을 고려하고 컴포넌트 자체에서 사용할지 전역 state 사용할지를 결정하면 올바른 state 관리를 할 수 있을 것입니다.