Redux 사용법

Jin·2022년 3월 1일
0

React

목록 보기
4/17
import { configureStore } from "@reduxjs/toolkit";
import DetailReducer from "./reducers/DetailReducer";
import HomeReducer from "./reducers/HomeReducer";
import SearchReducer from "./reducers/SearchReducer";
import TVReducer from "./reducers/TVReducer";

export const store = configureStore({
    reducer: {
        home: HomeReducer,
        tv: TVReducer,
        detail: DetailReducer,
        search: SearchReducer
    }
});

리덕스에서는 어플리케이션당 하나의 store만을 가져야 합니다.

@reduxjs/tookit이라는 npm을 설치하면 보다 쉽게 리덕스를 정의하실 수 있습니다. reducer라는 객체에 4개의 객체가 key - value 형태로 구성되어 있습니다.
리덕스도 결국 state를 전역으로 사용하기 위한 라이브러리이기에 나중에 우리는 state.home... state.tv... 이런 식으로 state 안에서 해당 컴포넌트에 필요한 값들을 가져오게 됩니다.

import React from "react";
import ReactDOM from "react-dom";
import App from "./Components/App";
import "./api";
import { Provider } from "react-redux";
import { store } from "./store";

ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById("root"));

index.js의 코드입니다.

provider를 사용하여 최상위 컴포넌트인 App을 감싸고 store라는 prop에 우리가 방금 생성한 store를 넣어줍니다.

import { createSlice } from "@reduxjs/toolkit"

export interface HomeState {
    nowPlaying: [] | null,
    upcoming: [] | null,
    popular: [] | null,
    error: string | null,
    loading: boolean
}

export const homeInitialState: HomeState = {
    nowPlaying: null,
    upcoming: null,
    popular: null,
    error: null,
    loading: true
}

const home = createSlice({
    name: 'homeReducer',
    initialState: homeInitialState,
    reducers: {
        success: (state, action) => {
            return {
                ...state,
                nowPlaying: action.payload.nowPlaying,
                upcoming: action.payload.upcoming,
                popular: action.payload.popular,
                loading: false
            }
        },
        fail: (state) => {
            return {
                ...state,
                error: "Can't find Home Information.",
                loading: false
            }
        },
        reset: () => homeInitialState
    }
})

export const { success, fail, reset } = home.actions;
export default home.reducer;

store를 선언하실 때 보셨겠지만 제가 예시로 들고 있는 프로젝트에는 home, tv, detail, search에 해당하는 reducer들이 존재합니다.
하지만 예시를 위해 비교적 간단한 HomeReducer를 보여드립니다. 타입스크립트를 적용하였기에 interface로 타입을 정의하고 initialState를 정의해줍니다.

리덕스 툴킷을 사용하면 createSlice라는 함수로 action과 reducer를 하나의 함수에 정의할 수 있다는 것입니다. 정말 간단한 정의 방법이므로 리덕스 툴킷을 함께 사용하실 것을 강추합니다!!

name에 적당한 값을 넣고 initialState에 초기값을 넣어주고 reducers에는 이제 몇 개의 경우의 수에 따른 state의 값을 넣어줍니다. 저는 success, fail, reset이라는 요소를 만들었고 state, action을 파라미터로 받는 함수에서 리턴시키는 값이 state를 대체하게 됩니다. 여기서 state의 범위는 state 안의 home 입니다. (전체 state 아님)

또한, 여기서 알아야 할 것이 action의 payload입니다. 우리가 어떤 값을 추가, 변경하고 싶을 때는 payload를 통해서 state를 대체하면서 발생시킬 수 있습니다. 어떻게 payload를 같이 사용하느냐는 좀 이따 같이 설명드리겠습니다.

리덕스 툴킷의 또 다른 장점이 바로 createSlice로 생성한 요소들을 부분적으로 선언하여 export 시킬 수 있다는 것입니다. 이러면 나중에 로직 부분에서 사용할 때 엄청 간편해집니다. 마찬가지로 default로 home.reducer를 export하였기에 store를 선언하는 부분에서 home: HomeReducer 이렇게 선언을 할 수 있는 것입니다.

import { useEffect } from "react";
import { useDispatch } from "react-redux";
import { moviesApi } from "../api";
import { fail, success, reset } from "../reducers/HomeReducer";

export function useHome(): void {
    const dispatch = useDispatch();

    const getHome = async () => {
        try {
            const { data: { results: nowPlaying } } = await moviesApi.nowPlaying();
            const { data: { results: upcoming } } = await moviesApi.upcoming();
            const { data: { results: popular } } = await moviesApi.popular();
            dispatch(success({ nowPlaying, upcoming, popular }));
        } catch {
            dispatch(fail());
        }
    }

    useEffect(() => {
        window.scrollTo(0, 0);
        getHome();

        return () => {
            dispatch(reset());
        }
    }, []);
}

이제 로직 부분입니다. 저는 useHome이라는 훅을 만들어서 로직을 담당하도록 하였습니다. 훅이라고 해서 거창할 것 같지만 사실 api를 통해 데이터를 불러오고 그것을 state에 넣어주는 것이 전부입니다.

useDispatch라는 훅을 통해 dispatch 함수를 사용할 수 있습니다. dispatch가 무엇인지가 관해서는 이미 많은 참고 자료가 있기 때문에 여기서는 간단히 action을 통해 state를 변경할 수 있도록 하는 함수라고 정의하고 넘어가겠습니다.

여기서 dispatch 함수를 호출하는 부분을 눈여겨 보셔야 합니다. dispatch 함수의 파라미터로 우리가 HomeReducer에서 선언하였던 action들이 함수 형태로 들어가고 그 action의 파라미터로 api에서 받아온 데이터들이 들어갑니다. 이 데이터들에 해당하는 것이 바로 action.payload입니다. 이 payload를 적재적소에 넣어줌으로써 state를 변경하는 것입니다.

간단하게 로직에 대해 설명드리자면, api를 통해 데이터를 잘 받아오면 그 데이터들을 state에 set해주고 실패하면 fail에 해당하는 action을 통해 state를 변경시킵니다. 이것은 useEffect를 통해 컴포넌트가 마운트되었을 때 실행되고 언마운트되기 전에 state를 리셋시켜줍니다.

이 로직이 이해가 안되신다면 제 useState, useEffect 게시물을 참고하시면 이해에 도움이 될 것입니다.
useState로 카운터 만들기
useEffect 사용법

이제 로직 부분이 끝이 났고 컴포넌트를 렌더하는 부분을 보여드리겠습니다.

import React from "react";
import Loader from "../Components/Loader";
import Helmet from "react-helmet";
import { useHome } from "../hooks/useHome";
import MovieResult from "../Components/MovieResult";
import Header from "../Components/Header";
import { shallowEqual, useSelector } from "react-redux";
import { HomeState } from "../reducers/HomeReducer";

interface Props {
    home: HomeState
}

function Home() {
    useHome();
    const { nowPlaying, upcoming, popular, error, loading } = useSelector((state: Props) => ({ ...state.home }), shallowEqual);

    return <>
        ...
        	html 구성 부분
        ...
    </>
}

export default Home;

react hook의 장점이 정말 코드가 간단하다는 것입니다. useHome을 호출하고 useSelector 훅을 통해 state.home 안에서 필요한 state들을 가져옵니다. 그리고 그것을 적절하게 화면에 보여주면 되는 것입니다.

여기서 주목해야 할 점이 제가 앞에서 말씀드렸다시피 우리가 바라보고 있는 것은 state.home이다 라는 것이고 useSelector의 첫번째 파라미터로 state.home에서의 state를 가져오는 함수를, 두번째 인자로 불필요한 렌더링을 막기 위해 shallowEqual을 선언해주었습니다.

이 shallowEqual을 렌더링 최적화를 위한 것으로 useMemo와 동일한 효과라고 보시면 됩니다. 즉, 이전 state와 현재 state가 동일할 경우, 리렌더링하지 않겠다는 것입니다.

이로써, 리덕스의 사용법에 대해 알아보았습니다. context api와 redux를 둘 다 사용해보니 리덕스를 사용할 때가 개발 편의성에서나, 코드 양에서나 모두 더 나았습니다. 무조건 리덕스를 사용해야 된다라는 것은 아니지만 그래도 컴포넌트가 2번 이상 state를 거쳐갈 때는 redux를 도입한다 같이 자신만의 기준을 세우고 개발하는 게 어떨까 합니다.

profile
배워서 공유하기

0개의 댓글