Firebase - Channel을 Redux Store에 등록하기(4)

Redux Global State

Glabal state

각각의 컴포넌트는 setState 또는 useState 훅을 이용하여 state를 가지게 된다. 이렇게 해서 생성된 state는 UI 렌더링에 사용되며 컴포넌트에 붙어있게 되며 컴포넌트가 언마운트되면 state 데이터는 사라지게 된다. 컴포넌트간 같은 state를 사용하는 경우 local이 아닌 global한 state를 만들어 사용하는 것이 편한데, React의 자체적인 Context API를 이용할 수 있지만 Dan Abramov와 Andrew Clark의 Redux를 사용하는 것이 Flux 패턴을 사용하기 때문에 작성과 사용하는 것이 더욱 간편해 보인다. Redux에는 단일 스토어가 존재하며, 스토어에 데이터를 저장하거나 가져오는 방법은 오직 액션(이라는 특별한 객체)을 디스패치(dispatch)하는 것이다. 액션을 디스패치하면 Reducer함수가 호출되고 새로운 객체를 반환하게 된다.

현재 채널을 Global state로 관리하려는 이유는 index(또는 home)페이지에 다양한 컴포넌트가 동시에 마운트되어 있고, 그 컴포넌트가 동시에 채널에 대한 데이터를 필요로 하기 때문이다. 따라서 현재 channel 데이터를 전역 객체로 등록하고 필요한 컴포넌트에서 가져다 쓰는 것이 가능해진다.

모듈 작성하기

Redux를 사용하기 위해서는 먼저 action 타입과 reducer 함수를 정의해야한다. 액션과 리듀서를 따로 파일을 두어 작성하는 방식도 있지만 한 파일에 channel과 관련된 코드를 묶어 모듈 형식으로 작성하는 방식이 있다.

// modules/channels.js

// 액션 타입을 먼저 변수로 만든다. 문자열을 액션타입으로 그대로 사용할 수도 있지만
// 변수로 관리하는 것이 개발자의 관리 영역을 더욱 넓혀준다고 생각한다.

// 액션 타입은 대문자로 하는 것이 일종의 Naming convention인 것 같다.
const SET_CURRENT_CHANNEL = "SET_CURRENT_CHANNEL";

// optional: action creator
/*
    따지고 보면 액션 생성함수는 필수가 아니다.
    하지만 액션을 생성하는 코드를 일일히 작성하면 본인만 피곤해지고
    유지보수에 어렵기 때문에 함수로 관리하는 것이 편할 것이다.

    다른 컴포넌트에서 dispatch와 함께 쓰일 것이기 때문에 export해준다.
*/
export const setCurrentChannel = (channel) => ({
  type: SET_CURRENT_CHANNEL,
  payload: channel,
});

// initial state
const initialState = {
  currentChannel: null,
};

export const channelReducer = (state = initialState, action) => {
    case SET_CURRENT_CHANNEL:
        return {
            ...state,
            currentChannel: action.payload
        }
    default:
        return state
}

combineReducer

Redux를 생성할 때, createStore(reducer, enhancer) 는 오직 하나의 리듀서만을 받는다. 따라서 rootReducer라고 한다. Redux의 단일 state안에는 channel 처럼 여러 state를 두고 있는데, 각각의 state와 대응되는(mapping) 리듀서를 구분하기 위해 combineReducer()를 이용하여 rootReducer를 만들어준다.

//  module/index.js
const rootReducer = combineReducers({
  user: userReducer, // 유저와 관련된 reducer
  channel: channelReducer, // 채널과 관련된 reducer
});

현재 channel을 Global state로 등록하기

Redux를 생성하고 모듈 코드 작성이 끝나면 컴포넌트 단에서 dispatch를 이용하여 현재 채널을 global state로 등록해야 한다. 컴포넌트에서 dispatch하기 위해서는 react-redux라는 라이브러리가 필요하다. 원래 애초에 Redux는 React용이 아니라 Global state management를 수행하는 라이브러리이기 때문에 React 컴포넌트에서 Redux를 사용하기 위해선 다른 라이브러리의 도움을 받아야 한다.

지금 구현하고자 하는 것은 channel 리스트 중에서 채널을 클릭하면 해당 채널을 Global state로 등록하는 것이다. 클릭과 관련되어있기 때문에 onClick 리스너로 등록해야 하고, dispatch(actionCreator)을 포함해야 한다. 여기서 dispatch와 관련된 코드를 함수로 한번 더 감싸서 새로 정의한 후 사용할 수 있지만 개인적으로 dispatch라는 단어가 Redux와 관련된 코드임을 보여준다고 생각하여 그대로 사용하는 것을 선호한다.

import { useDispatch } from "react-redux";
const dispatch = useDispatch();
const changeChannel = (ch) => {
  dispatch(setCurrentChannel(ch));
};

// onClick={() => changeChannel(ch)} 이렇게 사용한다.
const Channels = ({channels, changeChannel}) => {
    return (
        // ...

        {channels.map(ch => (
            <Menu.Item
                onClick={() => changeChannel(ch)}
            >
                {ch.name}
            </Menu.Item>
        ))}
    )
}

이런 식으로 컴포넌트 요소에 onClick 핸들러로 해당 채널을 global state로 등록하는 함수를 붙여서, 채널을 클릭하면 Redux에서 해당 채널을 관리할 수 있게 한다.

channel3

Redux devtools로 직접 확인해보면 channel과 관련된 데이터가 Redux state에 등록된 것을 볼 수 있다.

further readings