[React / Next] Redux에 관하여 알아보자 - 2. 설치/사용

    728x90
    반응형

     

     

    [React / Next] Redux에 관하여 알아보자 -  2. 설치/사용

    *설치 / 사용은 넥스트를 기준으로 했습니다.

    리액트와 사용법은 거의 비슷한데 next-redux-wrapper만 추가된 것이기에

    리액트만 사용하시는 분들도 저 라이브러리만 제외하고 사용하시면 될 것 같습니다.

     

     

    필요한 라이브러리들

    React-Redux

    React-Redux는 Redux 상태 관리 라이브러리를 React 애플리케이션과 통합하기 위한 공식 바인딩입니다.

    이 라이브러리는 React 컴포넌트에서 Redux 스토어에 쉽게 접근하고 상태를 사용하거나 액션을 디스패치할 수 있도록 도와줍니다.

    • Provider 컴포넌트: 애플리케이션의 최상위에 위치시켜 Redux 스토어를 React 컴포넌트 트리에 제공합니다.
    • connect 함수: Redux 스토어의 상태를 읽거나 액션을 디스패치할 수 있는 함수를 컴포넌트의 props로 매핑합니다.
    • Hooks: useSelectoruseDispatch 같은 훅을 제공하여 함수형 컴포넌트에서 Redux 스토어의 상태를 선택하거나 액션을 디스패치할 수 있습니다.

     

    @reduxjs/toolkit

    @reduxjs/toolkit은 Redux 개발을 보다 간단하고 효율적으로 만들기 위해 고안된 공식 툴킷입니다.

    이 라이브러리는 Redux의 복잡성을 줄이고, 보일러플레이트 코드를 최소화하는 데 초점을 맞추고 있습니다.

    • configureStore(): 스토어 설정을 간편화하며, Redux DevTools와 미들웨어 기본 설정을 자동으로 처리합니다.
    • createSlice(): 액션 타입, 액션 생성자 및 리듀서를 한 번에 생성할 수 있도록 도와주는 함수입니다. 이를 통해 관련 코드를 한 곳에 작성할 수 있어 유지 관리가 쉬워집니다. -- (Immer 라이브러리 활용)
    • createAsyncThunk: 비동기 로직을 처리하기 위한 Thunk를 생성하는 데 사용됩니다.
    • combineReducers: 여러 리듀서 함수를 하나의 리듀싱 함수로 결합하는 데 사용.

     

    next-redux-wrapper

    next-redux-wrapper는 Next.js 프레임워크에서 Redux를 쉽게 사용할 수 있도록 도와주는 라이브러리입니다.

    서버 사이드 렌더링(SSR) 또는 정적 사이트 생성(SSG) 시에 Redux 스토어를 동기화하고 초기 상태를 관리하는 데 필수적입니다.

    • 스토어의 자동 초기화: 각 페이지 요청에 대해 Redux 스토어를 자동으로 생성하고 초기화합니다.
    • HYDRATE 액션: 서버에서 생성된 초기 상태를 클라이언트의 스토어와 통합하기 위해 사용됩니다.
    • 간편한 통합: wrapper.withRedux를 사용하여 Next.js의 _app.js와 Redux 스토어를 쉽게 연결할 수 있습니다. -- OLD
    • 간편한 통합: 이제는 wrapper.useWrappedStore(rest) 를 사용합니다. -- NEW

     

     

     

    설치

    npm i react-redux next-redux-wrapper @reduxjs/toolkit

     

     

     

     

     

    사용

    1. 리듀서 모듈 만들기 ( user.js / post.js )

    /reducers/user.js

    // /reducers/user.js
    
    import { createSlice } from "@reduxjs/toolkit";
    import { HYDRATE } from "next-redux-wrapper";
    
    export const initialState = {
        isLoggedIn: false,
        user: null,
        signUpData: {},
        loginData: {},
    };
    
    const userSlice = createSlice({
        name: "user",
        initialState,
        reducers: {
            logIn: (state, action) => {
                state.isLoggedIn = true;
                state.user = action.payload;
            },
                logOut: (state, action) => {
                    state.isLoggedIn = false;
                    state.user = null;
                },
            },
        extraReducers: (builder) =>
            builder
                .addCase(HYDRATE, (state, action) => ({
                    ...state,
                    ...action.payload.user,
                })) // 서버에서 생성된 초기 상태를 클라이언트의 스토어와 통합
                .addDefaultCase((state) => state),
                // 완전히 선택적이며, 기본 동작을 명시적으로 표현하고 싶을 때,
                // 또는 특별한 기본 처리 로직이 필요할 때 고려해볼 수 있습니다.
                // 대부분의 경우, Redux Toolkit은 액션이 리듀서의 어떤 케이스에도 매치되지 않을 때
                // 자동으로 현재 상태를 반환하기 때문에 이를 사용할 필요가 없을 수도 있습니다.
    }); // 최신 방식
    // 기존에는 불변성 유지를 위해서 개발자가 새객체로 리턴해줘야했는데
    // redux toolkit에서는 immer라이브러리가 처리해줌.
    
    export const { logIn, logOut } = userSlice.actions; // 액션 생성 함수
    export default userSlice.reducer; // 리듀서

     

     

    /reducers/post.js

    import { createSlice } from "@reduxjs/toolkit";
    import { HYDRATE } from "next-redux-wrapper";
    
    export const initialState = {
    	mainPost: [],
    };
    
    const postSlice = createSlice({
        name: "post",
        initialState,
        reducers: {},
        extraReducers: (builder) =>
            builder
                .addCase(HYDRATE, (state, action) => ({
                    ...state,
                    ...action.payload.user,
                }))
                .addDefaultCase((state) => state),
    });
    
    export default postSlice.reducer;

     

     

    reducer들을 만들 때 기존에는 리덕스 상태의 불변성을 위해서

    액션 함수 (action creator)에서 새 객체를 리턴시켜줘야만 했다.

    그러나 redux-toolkit의 createSlice 함수를 사용하게 되면 내부적으로 immer 라이브러리를 활용하여 상태 업데이트를 처리합니다.

    이는 개발자가 직접 수정하는 것처럼 코드를 작성하지만, 실제로는 불변성을 유지하게 합니다.

    즉 Immer 라이브러리가 기존처럼 새로운 객체를 리턴해 줍니다.

    이는 Redux의 기본 원칙을 위배하지 않으면서도 보다 간결하고 이해하기 쉬운 코드를 작성할 수 있게 도와줍니다.

     

    그리고 기존에 HYDRATE를 사용하던 방법과 다르게 위처럼 redux-toolkit의 createSlice와 extraReducers를 하여 처리합니다.

    이는 최신방법으로 코드를 간결하게하고, 다양한 비동기 로직이나 특별한 처리가 필요한 액션을 통합하는데 효과적입니다. 

    HYDRATE  action은 서버에서 생성된 초기 상태를 클라이언트의 스토어와 통합

     

    추가로 reducer에서는 default 상태값을 리턴시켜줘야 해서 그렇게 했었는데

    툴킷에서는 액션이 리듀서의 어떤 케이스에도 매치되지 않을 때 자동으로 현재 상태를 반환하기 때문에 이를 고려하지 않아도 되지만 혹시나해서 .addDefaultCase((state) => state)를 추가했습니다.

     

     

     

    2. 리듀서 모듈 통합하기 ( index.js ) 

    ** toolkit 사용시 내부적으로 redux 라이브러리를 사용하기 때문에, combineReduces를  직접 호출하지 않아도 됩니다.

    /reducers/index.js
    
    import { combineReducers } from "@reduxjs/toolkit";
    import user from "./user"; // user.js의 reducer 불러온것
    import post from "./post"; // post.js의 reducer 불러온것
    
    const rootReducer = combineReducers({
    	user,
    	post,
    }); // 최신 코드
    
    export default rootReducer;

     

     

     

    3. 스토어 생성

    /store/configureStore.js

     

    combineRedurces를 사용했을 때 (합친 리듀서를 불러와서 적용)

    import { createWrapper } from "next-redux-wrapper";
    import { configureStore } from "@reduxjs/toolkit";
    import reducer from "../reducers"; // reducers/index의 rootReducer불러온 것
    
    // configureStore 자체에서 Redux DevTools 설정을 자동으로 처리합니다.
    const makeStore = () =>
        // configureStore: store 를 생성
        configureStore({
            reducer, // 리듀서 모듈들이 합쳐진 루트 리듀서
            middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
            // redux-toolkit 은 devTools 등의 미들웨어들을 기본적으로 제공
            // 추가 미들웨어나 enhancers가 필요하다면 여기에 포함시킬 수 있습니다.
        });
    
    const wrapper = createWrapper(makeStore, {
        // createWrapper: wrapper 생성, wrapper 에 store 바인딩
        debug: process.env.NODE_ENV === "development",
        // true이면 리덕스에 관한 좀 더 자세한 설명나옴 [개발때 true 추천]
    });
    
    export default wrapper;

     

     

    combineReducers 사용 안했을 때 (toolkit의 기본기능)

    import { createWrapper } from "next-redux-wrapper";
    import { configureStore } from "@reduxjs/toolkit";
    import reducer from "../reducers"; // reducers/index의 rootReducer불러온 것
    import user from "../reducers/user";
    import post from "../reducers/post";
    
    const makeStore = () =>
        // configureStore: store 를 생성
        configureStore({
            reducer: { <---- 여기서 리듀서들을 합쳐준다.
                user,
                post,
            }, // 리듀서 모듈들이 합쳐진 루트 리듀서
            middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
        });
    
    const wrapper = createWrapper(makeStore, {
    	debug: process.env.NODE_ENV === "development",
    });
    
    export default wrapper;

     

     

     

     

    4. _app.js (전체 공통 컴포넌트) 에 Redux 적용 

    /pages/_app.js

    import PropTypes from "prop-types";
    import Head from "next/head";
    import { Provider } from "react-redux";
    import wrapper from "../store/configureStore";
    
    const NodeBird = ({ Component, ...rest }) => {
        const { store, props } = wrapper.useWrappedStore(rest);
        const { pageProps } = props;
        return (
            <Provider store={store}>
                <Head>
                    <meta charSet='utf-8' />
                    <meta
                        name='viewport'
                        content='width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0'
                    />
                    <title>NodeBird</title>
                </Head>
                <Component {...pageProps} />
            </Provider>
        );
    };
    
    NodeBird.propTypes = {
        Component: PropTypes.elementType.isRequired,
        pageProps: PropTypes.any.isRequired,
    };
    
    // Next.js 프레임워크가 제공하는 웹 성능 지표를 기록하는 기능입니다.
    // 이 함수는 Next.js 애플리케이션의 성능을 측정하고 분석하는 데 사용
    export function reportWebVitals(metric) {
    	console.log(metric);
    }
    
    export default NodeBird; // High Order Component (HOC) 고차컴포넌트

     

    기존의 _app.js 컴포넌트를 wrapper.withRedux 고차함수에 인자로 넣고 새로운 컴포넌트를 리턴하는게 아니라

    redux의 provider 컴포넌트에 지금까지 만들어온 wrapper (configureStore.js에서 리턴)의 store을 넣어주고,

    component에는 pageProps를 내려준다.

     

    페이지 컴포넌트 렌더링: Component와 pageProps를 사용하여 각 페이지 별로 정의된 컴포넌트와 해당 페이지의 프롭스를 렌더링합니다.

    Component는 현재 활성화된 페이지의 실제 컴포넌트를,

    pageProps는 해당 페이지에 전달되는 데이터를 나타냅니다.

     

     

     

    5. 컴포넌트에서 사용해보기 

    /components/loginForm.js

    import {useDispatch, useSelector} from 'react-redux';
    import {logIn} from '../reducers/user' // user.js에서 export해준 액션 함수 불러오기
    
    const LoginForm = () => {
    	const dispatch = useDispatch();
        const {isLoggedIn} = useSelector((state) => state.user);
        // store의 데이터중 user안에 있는 isLoggedIn 데이터 불러와서 사용
        
        const onClickLogin = useCallback((data) => {
        	dispatch(login({id: data.userId, password: data.password}))
        }, [])
        
        return (
        	<div>
            	<p>{isLoggedIn}</p>
                <P onClick={onClickLogin}>로그인</p>
            </div>
        )
    }
    
    export default LoginForm;

     

     

     

     

     

     

     

     

     

    참조!

    https://velog.io/@h_jinny/Next.js-next-redux-wrapper-redux-toolkit-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

     

    [Next.js] next-redux-wrapper, redux-toolkit 사용하기

    redux-toolkit: 리덕스 툴킷(redux toolkit) 은 리덕스를 불편한 점을 보완하기위해 나온 개발 도구이다. 리덕스는 복잡한 저장소 구성, 수많은 라이브러리에 대한 의존성, 그리고 하나의 작업마다 많아

    velog.io

     

     

     

     

     

     

     

     

    728x90
    반응형

    댓글