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

Jayden1116님의 프로필 이미지

작성한 질문수

처음 만난 리덕스(Redux)

5강 요약

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

해결된 질문

23.07.14 16:39 작성

·

271

·

수정됨

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 (소플)
지식공유자

2023. 07. 14. 19:23

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

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

 

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
질문자

2023. 07. 14. 20:26

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