처음 만난 리덕스 (Redux) 문서


3.3 Store 관련 함수

지금부터는 Redux에서 제공하는 Store와 관련된 함수들을 하나씩 살펴보도록 하겠습니다.

3.3.1 createStore()

가장 먼저 나오는 함수는 createStore() 입니다.
createStore() 함수는 이름 그대로 Redux Store를 생성하는 역할을 하는 함수입니다.
Redux Store에는 Redux에서 관리하는 모든 상태값이 들어가게 됩니다.
그리고 Redux의 세 가지 원칙 중 첫 번째 원칙인 Single source of truth에 따라서, 하나의 애플리케이션에는 단 하나의 Store만 존재해야 합니다.

createStore() 함수의 파라미터는 다음과 같습니다.

createStore(reducer, [preloadedState], [enhancer])

먼저 첫 번째 파라미터로는 reducer가 들어갑니다.
우리가 앞에서 배운 것처럼, reducer는 Action이 발생하면 Action을 실제로 처리하는 역할을 합니다.
그리고 그 과정에서 State에 변화가 생기게 되죠.
다시 말하면, reducerRedux State에 변화를 주는 역할을 하는 Pure function을 의미합니다.

애플리케이션 규모가 커질수록 수많은 State와 Action들이 생기게 되고, 그러한 Action들을 처리하기 위한 Reducer들도 많아지게 됩니다.
그런 Reducer들을 합쳐서 하나로 만든 것을 rootReducer라고 부르는데, 여기 createStore() 함수의 첫 번째 파라미터로는 바로 이 rootReducer가 들어가게 됩니다.
만약 특정 Action을 처리하기 위한 Reducer가 여기에서 빠져있다면, 아무리 Action이 발생해도 State에 변화가 생기지 않을 것입니다.

그리고 createStore() 함수의 두 번째 파라미터로는 preloadedState가 들어갑니다.
preloadedState는 미리 로딩되어 있는 State라는 뜻인데, 쉽게 말하면 초기 상태값이 들어간다고 보면 됩니다.
여기서 파라미터의 이름을 initialState라고 하지 않고, preloadedState라고 한 이유는, 뒤에서 나올 redux-persist 등의 라이브러리를 사용하면 Redux Store 데이터를 다른 곳에 저장해놓았다가 초깃값으로 로드할 수 있기 때문입니다.
그런 의미에서 미리 불러온 값이라는 조금 더 범용적인 의미로 preloadedState라는 이름을 사용했다고 보면 됩니다.

그리고 마지막 세 번째 파라미터는 enhancer입니다.
향상시키다라는 의미를 가진 영어 단어 enhance를 사용해서, Redux Store의 기능을 향상 시켜주는 역할을 하는 함수들이 여기에 들어가게 됩니다.
그리고 이러한 역할을 하는 함수를 Store와 애플리케이션 중간에 있다는 의미로 middleware라고 부릅니다.

여기서 createStore() 함수의 두 번째와 세 번째 파라미터는, 필수가 아니라 optional 파라미터이기 때문에 이렇게 대괄호로 감싼 것을 볼 수 있습니다.
createStore() 함수의 파라미터들을 잘 기억하면서 실제 예제 코드를 한 번 보도록 하겠습니다.

import { createStore } from 'redux';

function todoReducer(state = [], action) {
    switch (action.type) {
        case 'ADD_TODO':
            return state.concat([action.text]);
        default:
            return state;
    }
}

const store = createStore(todoReducer, ['처음 만난 리덕스']);

store.dispatch({
    type: 'ADD_TODO',
    text: '리덕스 강의 열심히 듣기'
});

// 출력 결과
// ['처음 만난 리덕스', '리덕스 강의 열심히 듣기']
console.log(store.getState());

이 코드는 실제로 Redux의 createStore() 함수를 사용하는 예제 코드입니다.
TODO 리스트 앱에서 Redux를 사용한 예시 코드이고, Action으로는 할 일을 추가하는 ADD_TODO Action 하나만 존재합니다.

여기서 우리가 눈여겨봐야할 부분은 바로 createStore() 함수를 호출하는 부분입니다.
첫 번째 파라미터로 todoReducer라는 함수. 즉, Reducer가 들어갔고,
두 번째 파라미터로 할 일을 나타내는 문자열들의 배열. 즉, 초기 상태값이 들어갔습니다.
그리고 마지막 세 번째 파라미터인 enhancer는 사용하지 않고 생략되었습니다.

이렇게 Redux Store를 생성하면 ADD_TODO라는 Action이 발생할 때마다 할 일 목록에 할 일들이 하나씩 추가되며, 이 할 일 목록들은 모두 Redux Store에서 관리됩니다.
그리고 Action을 발생시키지 않고 Store의 데이터를 변경하는 것은 Redux의 원칙에도 위반되고 불가능합니다.

3.3.2 applyMiddleware()

다음으로는 방금 전 createStore() 함수의 세 번째 파라미터였던 enhancer. 즉, middleware를 적용하기 위해 필요한 applyMiddleware()라는 함수에 대해서 배워보도록 하겠습니다.

applyMiddleware(...middleware)

applyMiddleware() 함수의 파라미터로는 middleware들이 들어갑니다.
여기서 을 사용한 것은 JavaScript의 Spread 문법을 사용한 것입니다.
배열에 들어있는 변수들을 꺼내서 펼쳐주는 역할을 하는 문법이죠.
그래서 결국 applyMiddleware() 함수에는 필요한 middleware들을 콤마로 구분해서 쭉 넣으면 됩니다.

그렇다면 middleware는 정확히 무엇일까요?
쉽게 말하면, middlewareRedux에 원하는 기능을 추가할 수 있게 해주는 함수입니다.
그리고 이 함수는 정해진 규칙에 따라 Redux Store의 dispatch()getState() 함수가 포함 된 객체를 파라미터로 받게 됩니다.

middleware를 사용하면 Redux Store에서 Action이 처리될 때 함께 작동하길 원하는 코드를 끼워 넣을 수 있습니다.
그리고 여러 개의 middleware들은 하나로 합쳐질 수 있습니다.

middleware가 Redux에서 필수적인 것은 아닙니다.
하지만 middleware를 잘 사용하면 Redux를 더 효과적으로 사용할 수 있기 때문에, 대표적인 middleware들은 한 번씩 사용해보는 것이 좋습니다.

그리고 이 applyMiddleware() 함수의 리턴 값은 주어진 middleware들이 적용 된 Store enhancer 함수가 됩니다.
여러 개의 middleware들이 하나로 합쳐져서 나오는 것이라고 보면 됩니다.

NOTE:
아래 링크는 앞에 나왔던 JavaScript Spread 문법에 관한 링크입니다.
Spread 문법이 생각나지 않거나 헷갈리는 분들은 한 번씩 들어가서 확인해보기 바랍니다.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

그럼 applyMiddleware() 함수를 실제로 사용하는 예제 코드를 한 번 볼까요?

import { createStore, applyMiddleware } from 'redux';
import todoReducer from './reducers';

function loggerMiddleware({ getState }) {
    return next => action => {
        console.log('dispatch 예정 action', action);

        // Middleware chain에 있는 다음 dispatch 함수를 호출
        const returnValue = next(action);

        console.log('dispatch 이후 state', getState());
        return returnValue;
    }
}

const store = createStore(todoReducer, ['처음 만난 리덕스'], applyMiddleware(loggerMiddleware));

store.dispatch({
    type: 'ADD_TODO',
    text: '리덕스 강의 열심히 듣기'
});
// 아래와 같이 loggingMiddleware에 의해 로깅됨:
// dispatch 예정 action, { type: 'ADD_TODO', text: '리덕스 강의 열심히 듣기' }
// dispatch 이후 state, ['처음 만난 리덕스', '리덕스 강의 열심히 듣기']

이 코드는 앞에 나왔던 코드와 동일한 코드인데, createStore() 함수의 세 번째 파라미터로 applyMiddleware() 함수를 사용해서 loggerMiddleware라는 middleware를 넣어준 코드입니다.
그리고 reducer는 외부에 별도의 파일로 작성한 이후에 import 해주었습니다.
loggerMiddleware는 Action이 발생했을 때 Redux store의 state 변화를 logging해주는 middleware라고 보시면 됩니다.

이런 식으로 applyMiddleware() 함수를 사용해서 여러 개의 middleware를 Redux에 끼워넣을 수 있습니다.
대표적으로 사용되는 오픈소스 형태의 middleware들에 대해서는 뒤에 나오는 강의에서 하나씩 자세히 알아볼 예정입니다.
여기에서는 middleware가 어떻게 Redux Store에 주입되는지, 그 과정에 대해서만 잘 이해하고 넘어가기 바랍니다.

3.3.3 getState()

다음으로 나오는 Redux Store와 관련되 함수는 getState() 함수입니다.
getState() 함수는 이름 그대로 State를 가져오는 역할을 하는 함수입니다.

이 때 가져오는 State는 앞에서 본 것처럼, Redux Store에 저장된 State Tree가 됩니다.

Redux State Tree

이 Root State Tree를 통해서 Redux에 있는 모든 데이터에 접근이 가능한 것이죠.

앞에서 나왔던 코드를 다시 보면, 이미 getState() 함수를 사용하고 있는 것을 볼 수 있습니다.

import { createStore, applyMiddleware } from 'redux';
import todoReducer from './reducers';

function loggerMiddleware({ getState }) {
    return next => action => {
        console.log('dispatch 예정 action', action);

        // Middleware chain에 있는 다음 dispatch 함수를 호출
        const returnValue = next(action);

        console.log('dispatch 이후 state', getState());
        return returnValue;
    }
}

const store = createStore(todoReducer, ['처음 만난 리덕스'], applyMiddleware(loggerMiddleware));

store.dispatch({
    type: 'ADD_TODO',
    text: '리덕스 강의 열심히 듣기'
});

console.log(store.getState());

여기에서의 getState() 함수는 middleware에 파라미터로 전달된 것입니다.
만약 middleware 내부가 아닌 외부에서 State Tree를 가져오려면, 만든 Redux Store를 사용해서 store.getState() 형태로 호출하면 됩니다.

3.3.4 dispatch()

마지막으로 나오는 Store 관련 함수는 dispatch() 함수입니다.
dispatch() 함수는 Action 객체를 파라미터로 받아서, 실제 Action을 발송하는 역할을 합니다.
이 함수가 바로 앞에서 나온 Dispatcher의 역할을 하는 것이죠.
결과적으로 Dispatcher를 통해 Action이 발송되고 그것을 처리하는 과정에서 Redux Store의 State에 변화가 생기게 됩니다.

import { createStore, applyMiddleware } from 'redux';
import todoReducer from './reducers';

function loggerMiddleware({ getState }) {
    return next => action => {
        console.log('dispatch 예정 action', action);

        // Middleware chain에 있는 다음 dispatch 함수를 호출
        const returnValue = next(action);

        console.log('dispatch 이후 state', getState());
        return returnValue;
    }
}

const store = createStore(todoReducer, ['처음 만난 리덕스'], applyMiddleware(loggerMiddleware));

store.dispatch({
    type: 'ADD_TODO',
    text: '리덕스 강의 열심히 듣기'
});

위 코드의 제일 아래쪽에 있는 store.dispatch()가 바로 Action을 dispatch하는 코드입니다.

여기에서는 ADD_TODO라는 type‘리덕스 강의 열심히 듣기’라는 text를 가진 Action 객체를 생성해서 전달하고 있습니다.
이렇게 dispatch된 action은 Reducer에서 처리됩니다.

Action과 Reducer에 대해서는 뒤에서 더 자세히 배울 예정이기 때문에, 여기에서는 Action이 이렇게 발생하고 그걸 Reducer에서 처리한다 정도만 알고 넘어가도록 하겠습니다.


마지막 업데이트: 2023년 07월 14일 00시 00분

이 문서의 저작권은 이인제(소플)에 있습니다. 무단 전재와 무단 복제를 금합니다.