React
는 상태를 기반으로 사용자 인터페이스(UI
)를 제어하기 위한 자바스크립트 라이브러리이다. 웹 애플리케이션은 사용자와의 활발한 인터렉션을 처리하고, 실시간 통신을 통해 데이터를 받아오며 이를 기반으로 렌더링을 하는 등 다양한 기능을 가지고 있다. 이때, 상태(state)를 관리하는 것은 필수적으로 요구되는 사항이다.
즉, 상태 관리란 React
앱 구축에 있어 핵심이 되는 부분 중 하나라 할 수 있다. 따라서 상태 관리를 위한 많은 기술이 다양한 라이브러리의 등장으로 이어지게 되었다. 그 중에서도 리덕스(Redux
)는 리액트와 가장 오랜 기간을 함께했으며 가장 널리 알려진 상태관리 라이브러리 중 하나이다. 이번 포스팅에서는 리덕스의 동작 원리를 간단한 예제와 함께 살펴볼 것이다.
👉 리덕스의 코어 : createStore
약 25줄의 코드이지만 리덕스의 핵심 기능이 포함되어 있는 createStore
함수를 살펴보자.
createStore
에서는 현재 상태 값과 여러 구독자를 추적하고, 값을 업데이트하고, 작업이 전달될 때 구독자에게 알리고, 상태 값을 반환 받는 API를 가지고 있다.
function createStore(reducer) {
var state;
var listeners = [];
function getState() {
return state;
}
function subscribe(listener) {
listeners.push(listener);
return function unsubscribe() {
var index = listeners.indexOf(listener);
listeners.splice(index, 1);
}
}
function dispatch(action) {
state = reducer(state, action);
listeners.forEach(listener => listener());
}
dispatch({});
return { dispatch, subscribe, getState };
}
👉 하나씩 살펴보는 Redux Core
먼저, 스토어는 다음과 같은 값을 가지고 있다.
- 저장소에 저장할 상태 값 :
state
- 상태 값을 구독하고 있는(변경을 감지) 구독자들을 저장하는 목록 :
listeners
배열- 해당
listner
들은() => {}
형식의 콜백 함수 dispatch
함수가 호출될 때마다 실행된다.
- 해당
모든 상태 값은 하나의 저장소(store
) 안에 있는 객체 트리에 저장된다. 상태 트리를 변경하는 유일한 방법은 무엇이 일어날지 서술하는 객체인 액션(action
)을 보내는 것 뿐이다. 액션이 상태 트리를 어떻게 변경할지 명시하기 위해 사용자는 리듀서(reducer
)를 작성해야 한다.
function createStore(reducer) {
var state;
var listeners = [];
// ...
}
getState()
getState
함수는 리덕스 저장소에 저장된 상태 값 state
를 반환한다.
function getState() {
return state;
}
subscribe(listener)
subscribe
함수는 상태값이 변경되었을 때 실행 될 콜백함수 listner
를 받아서 listners
배열에 추가한다. 해당 listner
를 listners
배열에서 삭제하는 구독 해지 함수 unscribe
를 반환한다.
function subscribe(listener) {
listeners.push(listener);
return function unsubscribe() {
var index = listeners.indexOf(listener);
listeners.splice(index, 1);
}
}
dispatch(action)
dispatch
함수는 action
을 받아서 reducer
함수를 통해 state
를 변경한다. 이후 listners
배열의 listner
를 하나씩 호출하면서 상태가 변경되었음을 알린다.
function dispatch(action) {
state = reducer(state, action);
listeners.forEach(listener => listener());
}
반환값
createStore
내부에서 dispatch({})
를 호출함으로써 reducer
함수에서 action
이 빈 객체로 주어졌을 경우, 반환하는 값으로 state
를 초기화해준다. 이후 위에서 설명한 dispatch
, subscribe
, getState
함수를 담은 객체를 반환한다.
해당 함수들은 모두 클로저 함수이다. 따라서, createStore
에서 관리하는 값(state
, listeners
)은 외부에서 접근하지 못하며, 현재 상태를 기억하고 함수를 통해 상태가 변경되어도 최신 상태가 유지된다.
function createStore(reducer) {
// ...
dispatch({});
return { dispatch, subscribe, getState };
}
👉 리덕스 기본 예제
리덕스의 동작 핵심 코드만 살펴보았을 때 이해가 잘 되지 않을 수도 있다. 따라서 아래 예제를 통해 다시 살펴보도록 하자.
import { createStore } from 'redux'
/**
* 이것이 (state, action) => state 형태의 순수 함수인 리듀서입니다.
* 리듀서는 액션이 어떻게 상태를 다음 상태로 변경하는지 서술합니다.
*
* 상태의 모양은 당신 마음대로입니다: 기본형(primitive)일수도, 배열일수도, 객체일수도,
* 심지어 Immutable.js 자료구조일수도 있습니다. 오직 중요한 점은 상태 객체를 변경해서는 안되며,
* 상태가 바뀐다면 새로운 객체를 반환해야 한다는 것입니다.
*
* 이 예제에서 우리는 `switch` 구문과 문자열을 썼지만,
* 여러분의 프로젝트에 맞게
* (함수 맵 같은) 다른 컨벤션을 따르셔도 좋습니다.
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
// 앱의 상태를 보관하는 Redux 저장소를 만듭니다.
// API로는 { subscribe, dispatch, getState }가 있습니다.
let store = createStore(counter)
// subscribe()를 이용해 상태 변화에 따라 UI가 변경되게 할 수 있습니다.
// 보통은 subscribe()를 직접 사용하기보다는 뷰 바인딩 라이브러리(예를 들어 React Redux)를 사용합니다.
// 하지만 현재 상태를 localStorage에 영속적으로 저장할 때도 편리합니다.
store.subscribe(() => console.log(store.getState())))
// 내부 상태를 변경하는 유일한 방법은 액션을 보내는 것뿐입니다.
// 액션은 직렬화할수도, 로깅할수도, 저장할수도 있으며 나중에 재실행할수도 있습니다.
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1
Reducer 함수 : counter
counter
함수는 createStore
에 넘겨줄 reducer
함수이다.
action.type
의 값이 default 일 경우 state
를 반환하고 있다. 따라서 createStore
내부에서 dispatch({})
를 실행하면 state
의 값이 0
으로 초기화 될 것이다.
이후 dispatch({type: 'INCREMENT'})
를 호출하면 state
값이 +1
될 것이고, dispatch({type: 'DECREMENT'})
를 호출하면 state
값이 -1
될 것이다.
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
Subscribe
store
의 subscribe
메서드에 () => console.log(store.getState())
의 함수(listner
)를 넘겨주었다. 해당 함수는 앞으로 store
의 dispatch
가 호출될 때마다 콘솔에 변경된 state
의 값을 출력할 것이다.
store.subscribe(() => console.log(store.getState())))
따라서 아래와 같이 dispatch
함수를 호출하면 state
값이 초기값 0
에서 +1
되고, 해당 값은 콘솔에 출력된다.
store.dispatch({ type: 'INCREMENT' })
참고
'Frontend' 카테고리의 다른 글
React-Query 정리 (0) | 2022.08.05 |
---|---|
[Javascript] Throttle 과 Debounce (0) | 2022.05.12 |
[React] ContextAPI & useContext Hook을 통한 Global State 값 관리하기 (0) | 2022.03.11 |
[Javascript] 변수, 호이스팅, TDZ (0) | 2022.03.07 |
[Typescript] Advanced-Types & Utility-Types 정리 (0) | 2022.03.02 |