인프런 커뮤니티 질문&답변

Jayden1116님의 프로필 이미지
Jayden1116

작성한 질문수

처음 만난 리덕스(Redux)

5강 요약

createStore()와 combineReducers()에 대한 질문

해결된 질문

작성

·

299

·

수정됨

1

각각의 todoReducer, memoReducer에 initialState를 전달하는 방법말고 createStore() 함수의 2번째 인자로 preloadedState를 전달하는 방법은 없나요?

const preloadedState = {
  todo: initialTodoState,
  memo: initialMemoState,
};

const store = createStore(
  rootReducer,
  preloadedState,
  applyMiddleware(loggerMiddleware),
);

(추가) 위와 같이 작성 후(각 reducer의 초기 상태 전달 안함), 애플리케이션이 동작하지 않더라구요..!

(추가) 해서 GPT에게 물어봤습니다! 그랬더니 답변으로


이 방법을 사용하면 preloadedState를 통해 전체 앱의 초기 상태를 한 곳에서 관리할 수 있어 유용합니다. 하지만 이 방법은 각 리듀서에서 초기 상태를 설정하는 방법과 병행해서 사용해야 합니다. 왜냐하면 preloadedState는 앱이 시작될 때 한 번만 사용되고, 그 이후에는 각 리듀서에서 정의한 초기 상태가 사용되기 때문입니다.


해서 각 reducer에도 각각의 initialState 배열을 전달하니 동작은 하긴 하는데, 이럴꺼면 굳이 preloadedState를 전달하는 의미가 없는 것 같아서 조금 헷갈리네요..!

결론적으로 질문은 아래와 같습니다..!

1) 각 reducer에게 초기 상태를 전달하지않고 combine한 reducer를 createStore에 전달할 때, 초기 상태를 전달해서 사용하는 방법이 있나요?

답변 1

1

Inje Lee (소플)님의 프로필 이미지
Inje Lee (소플)
지식공유자

안녕하세요, 소플입니다.

제가 질문을 제대로 이해한 것인지 모르겠지만, 일단 답변을 드려보겠습니다.

 

Redux Store를 생성할 때,

  1. 먼저 combineReducers() 함수를 사용해서 rootReducer를 만들고,

  2. 그 이후에 createStore() 함수를 호출하게 됩니다.

그런데 combineReducers() 함수가 호출되는 시점에 Redux가 내부적으로 각 Reducer에 대해서 초기 상태 값을 수집하기 위해서 Action 호출을 하게 됩니다.

이 때도 Reducer는 역시 상태 값을 반환해야 하는데,

상태 값이 정의되어 있지 않기 때문에 따로 설정해주지 않으면 에러가 나게 됩니다.

그래서 각 Reducer의 state 파라미터에 아래와 같이 초기 상태값을 넣어주는 것입니다.

function todoReducer(state = todoInitialState, action) {
    switch (action.type) {
        case ACTION_TYPE_ADD_TODO:
            return state.concat(action.text);
        case ACTION_TYPE_REMOVE_TODO:
            return state.slice(0, -1);
        case ACTION_TYPE_REMOVE_ALL:
            return [];
        default:
            return state;
    }
}

그래서 질문하신 것처럼 각 Reducer에 초기 상태값을 전달하지 않고 preloadedState를 사용하는 방법은 함수가 호출되는 순서상 불가능하다 라고 보면 됩니다.

 

그리고 정확한 이해를 위해서 아래 코드와 같이 각 Reducer에 로그를 넣어서 호출해보시면 좋습니다.

<!DOCTYPE html>
<html>
    <head>
        <title>처음 만난 리덕스 - TODO</title>
        <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
    </head>
    <body>
        <h3>오늘 할 일</h3>
        <ul id="todo-list"></ul>

        <div>
            <input id="input-text" />
            <button id="add-button">할 일 추가</button>
            <button id="remove-button">할 일 삭제</button>
            <button id="remove-all-button">모두 삭제</button>
            <button id="logging-state">State Logging</button>
        </div>

        <h3>메모</h3>
        <ul id="memo-list"></ul>

        <div>
            <input id="input-memo-text" />
            <button id="add-memo-button">메모 추가</button>
            <button id="remove-memo-button">메모 삭제</button>
        </div>

        <script>
            // TODO 관련 Action Type
            var ACTION_TYPE_ADD_TODO = 'ADD_TODO';
            var ACTION_TYPE_REMOVE_TODO = 'REMOVE_TODO';
            var ACTION_TYPE_REMOVE_ALL = 'REMOVE_ALL';
            // MEMO 관련 Action Type
            var ACTION_TYPE_ADD_MEMO = 'ADD_MEMO';
            var ACTION_TYPE_REMOVE_MEMO = 'REMOVE_MEMO';

            var todoInitialState = [];
            var memoInitialState = [];

            function todoReducer(state = todoInitialState, action) {
                console.log('todoReducer', state, action);
                switch (action.type) {
                    case ACTION_TYPE_ADD_TODO:
                        return state.concat(action.text);
                    case ACTION_TYPE_REMOVE_TODO:
                        return state.slice(0, -1);
                    case ACTION_TYPE_REMOVE_ALL:
                        return [];
                    default:
                        return state;
                }
            }

            function memoReducer(state = memoInitialState, action) {
                console.log('memoReducer', state, action);
                switch (action.type) {
                    case ACTION_TYPE_ADD_MEMO:
                        return state.concat(action.text);
                    case ACTION_TYPE_REMOVE_MEMO:
                        return state.slice(0, -1);
                    default:
                        return state;
                }
            }

            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;
                };
            }

            console.log("combineReducers")
            var rootReducer = Redux.combineReducers({
                todo: todoReducer,
                memo: memoReducer,
            });

            var store = Redux.createStore(
                rootReducer,
                Redux.applyMiddleware(loggerMiddleware)
            );

            var todoListElem = document.getElementById('todo-list');
            var memoListElem = document.getElementById('memo-list');
            var inputElem = document.getElementById('input-text');
            var inputMemoElem = document.getElementById('input-memo-text');

            function render() {
                // 이전 TODO, MEMO 목록 초기화
                todoListElem.innerHTML = '';
                memoListElem.innerHTML = '';

                // TODO 목록 렌더링
                store.getState().todo.forEach((todo) => {
                    const todoListItemElem = document.createElement('li');
                    todoListItemElem.textContent = todo;
                    todoListElem.appendChild(todoListItemElem);
                });

                // MEMO 목록 렌더링
                store.getState().memo.forEach((memo) => {
                    const memoListItemElem = document.createElement('li');
                    memoListItemElem.textContent = memo;
                    memoListElem.appendChild(memoListItemElem);
                });
            }

            render();
            store.subscribe(render);

            function addTodoActionCreator(text) {
                return {
                    type: ACTION_TYPE_ADD_TODO,
                    text: text,
                };
            }

            function removeTodoActionCreator() {
                return {
                    type: ACTION_TYPE_REMOVE_TODO,
                };
            }

            function removeAllActionCreator() {
                return {
                    type: ACTION_TYPE_REMOVE_ALL,
                };
            }

            function addMemoActionCreator(text) {
                return {
                    type: ACTION_TYPE_ADD_MEMO,
                    text: text,
                };
            }

            function removeMemoActionCreator() {
                return {
                    type: ACTION_TYPE_REMOVE_MEMO,
                };
            }

            document
                .getElementById('add-button')
                .addEventListener('click', function () {
                    // Action을 실제로 dispatch
                    store.dispatch(addTodoActionCreator(inputElem.value));

                    // Input 초기화
                    inputElem.value = '';
                });

            document
                .getElementById('remove-button')
                .addEventListener('click', function () {
                    store.dispatch(removeTodoActionCreator());
                });

            document
                .getElementById('remove-all-button')
                .addEventListener('click', function () {
                    store.dispatch(removeAllActionCreator());
                });

            document
                .getElementById('logging-state')
                .addEventListener('click', function () {
                    console.log('현재 state', store.getState());
                });

            document
                .getElementById('add-memo-button')
                .addEventListener('click', function () {
                    store.dispatch(addMemoActionCreator(inputMemoElem.value));
                    inputMemoElem.value = '';
                });

            document
                .getElementById('remove-memo-button')
                .addEventListener('click', function () {
                    store.dispatch(removeMemoActionCreator());
                });
        </script>
    </body>
</html>

위와 같이 코드를 작성해서 애플리케이션을 실행하게 되면,

아래와 같은 로그가 처음에 출력되는 것을 볼 수 있습니다.

image

combineReducers() 함수가 호출되는 시점에 이미 Redux가 내부적으로 각 Reducer에 대해서 초기 상태 값을 수집하는 것이죠.

혹시 추가로 궁금한 부분이 있다면 댓글 달아주시기 바랍니다!

 

감사합니다.

Jayden1116님의 프로필 이미지
Jayden1116
질문자

와... reducer에서 log를 넣어서 확인하니까 넘 명확하고 좋네요!!!!!
매번 답변 잘해주셔서 넘넘 감사드립니다 ㅠㅠㅠ!!!

Jayden1116님의 프로필 이미지
Jayden1116

작성한 질문수

질문하기