서론
Redux는 애플리케이션의 상태 관리를 단순화하기 위한 JavaScript 라이브러리이다. Redux의 핵심은 모든 상태 변경을 액션이라는 명확한 객체로 설명하는 것이다. 이를 통해 상태가 어떻게, 왜 변경되는지 명확히 알 수 있다. Redux의 철학은 “변화가 발생했다면, 그 이유를 알 수 있어야 한다”는 것이다.
이 글에서는 Redux의 기본 컨셉들과 사용법에 대해 알아보고자 한다
Action: 상태 변경의 설명서
Action은 애플리케이션에서 발생하는 일을 설명하는 JavaScript 객체이다. Redux에서 모든 상태 변경은 Action 객체에 의해 발생한다. Action은 type 속성을 반드시 가지고 있으며, 상태 변경에 필요한 추가 데이터를 포함할 수 있다.
{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
이처럼 액션을 사용하면 상태가 변경된 이유를 추적할 수 있다.
Reducer: 상태와 액션을 연결하는 함수
reducer는 상태와 액션을 받아 새로운 상태를 반환하는 순수 함수이다. reducer의 핵심 역할은 이전 상태와 액션에 따라 다음 상태를 결정하는 것이다. 예를 들어, todos라는 reducer를 정의하면 다음과 같다.
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([{ text: action.text, completed: false }])
case 'TOGGLE_TODO':
return state.map((todo, index) =>
action.index === index
? { text: todo.text, completed: !todo.completed }
: todo
)
default:
return state
}
}
여러 Reducer를 하나로 합치기
reducer는 일반적으로 상태의 특정 부분을 담당한다. 이를 통합해 애플리케이션의 전체 상태를 관리하려면 여러 reducer를 하나의 reducer로 결합해야 한다. 다음은 todoApp이라는 reducer 예시이다.
function todoApp(state = {}, action) {
return {
todos: todos(state.todos, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action)
}
}
Store: 상태를 관리하는 저장소
Redux 애플리케이션의 상태를 저장하고 관리하는 핵심은 스토어이다. createStore 함수에 리듀서를 전달하면 스토어가 생성된다. 스토어는 상태를 읽고, 액션을 디스패치하고, 상태 변경을 구독할 수 있다.
스토어는 Middleware와 Enhancer를 통해 확장 가능하다. 미들웨어는 액션 디스패치의 추가 동작을 정의하며, 확장자는 스토어 자체의 기능을 확장한다. 보통 애플리케이션을 만들 때 Redux 애플리케이션에서 스토어를 설정할 때, createStore 대신 configureStore를 사용하는 것이 권장된다. configureStore는 Redux Toolkit에서 제공하는 유틸리티 함수로, 일반적인 Redux 스토어 설정 작업을 간소화하고, 기본적으로 개발 편의성을 높이는 여러 기능을 포함한다.
Selector: 효율적인 상태 조회
셀렉터는 Redux 상태에서 필요한 데이터를 추출하거나 가공하는 함수이다. 단순한 상태 조회뿐만 아니라, 복잡한 계산을 캡슐화하여 재사용성과 성능을 높인다. 셀렉터는 상태를 인자로 받는 함수이며 별도의 라이브러리가 필요하지 않지만, 보통 useSelector혹은 reselect 라이브러리와 사용되며, 상태 변경 시 불필요한 재계산을 방지한다.
const getVisibleTodos = (state) => {
return state.todos.filter(todo =>
state.visibilityFilter === 'SHOW_ALL' ||
(state.visibilityFilter === 'COMPLETED' && todo.completed)
)
}
useSelector는 Redux의 상태를 받아 특정 값을 반환하는 hook으로, 컴포넌트를 Redux 상태에 연결한다. 내부적으로 React Context API와 Observer 패턴을 사용하여 동작한다.
function useSelector(selector, equalityFn = refEquality) {
const { store, subscription: contextSub } = useReduxContext();
return useSelectorWithStoreAndSubscription(
selector,
equalityFn,
store,
contextSub
);
}
useReduxContext는 Context API를 이용하여 Redux 스토어와 Subscription 객체를 제공받는다. 이 Context는 Provider 컴포넌트를 통해 애플리케이션에 전달된다. useSelector의 반환값인 useSelectorWithStoreAndSubscription는 내부적으로 shallow compare를 이용해 selector가 바뀌었는지 비교하여, 바뀌지 않았다면 memoization된 ref를 반환한다.
따라서 useSelector를 사용할 때 주의할 점은 컴포넌트 내부에서 useSelector를 선언할 경우, 컴포넌트가 재렌더링 될때마다 함수는 새로 선언되며, 이때 useSelector내부의 callback함수는 객체이므로(javascript에서 함수는 일급 객체임을 생각해보자) state가 변경될 때 selector !== latestSelector.current(shallow compare)가 항상 true가 되고 selector는 매번 실행될 것이다. 이때문에 selector를 컴포넌트 외부에서 선언하거나, reselect와 같은 라이브러리를 사용하는 것이 중요하다.
결론
Redux는 애플리케이션의 상태 관리와 상태 변경의 명확성을 극대화하기 위한 도구이다. 액션, 리듀서, 스토어, 셀렉터와 같은 핵심 개념을 통해 복잡한 상태 관리도 체계적으로 처리할 수 있다. Redux의 가장 큰 장점은 순수 JavaScript로 작성되며, 상태 변경이 왜 발생했는지를 명확히 알 수 있다는 점이다.
참고자료
- GoIdle. In-depth Redux. Accessed on November 29, 2024. https://goidle.github.io/redux/in-depth-redux/
- Redux.js Documentation. Deriving Data and Using Selectors. Accessed on November 29, 2024. https://redux.js.org/usage/deriving-data-selectors
- Redux.js Documentation. Introduction to Redux. Accessed on November 29, 2024. https://redux.js.org/introduction/getting-started
- Redux.js Documentation. Core Concepts. Accessed on November 29, 2024. https://redux.js.org/introduction/core-concepts
'Library, Tool' 카테고리의 다른 글
Redux Middleware와 dispatch의 관계 (0) | 2024.11.26 |
---|---|
TypeScript: type과 Interface의 차이 (4) | 2024.11.04 |
Frontend에서의 Bundler (0) | 2024.09.25 |
웹 성능을 높일 수 있는 bundler plugins (2) | 2024.09.11 |
Git의 동작원리 (0) | 2024.07.15 |