본문 바로가기

TOPIC

React 프로젝트에 Redux 적용하기 : Redux란??

반응형

다음 Redux는 React.Hook 타입을 기본으로 적용하였습니다.

 

리액트에는 데이터를 전달하는 요소들이 다음 표와 같이 있습니다.

구분 설명
프로퍼티 상위 컴포넌트에서 하위 컴포넌트로 전달되는 읽기 전용 데이터입니다.
state 컴포넌트의 state를 저장하고 변경할 수 있는 데이터입니다.
컨텍스트 부모 컴포넌트에서 생성하여 모든 자식 컴포너넌트에 전달하는 데이터입니다.
리덕스  서버에서 받은 데이터를 앱 전체에 전달하거나 관리합니다.

컨텍스트와 리덕스가 서로 비슷해 보일수 도 있다. 최상위 컨텍스트에서 서버에서 요청하고 데이터를 핸들링하는 로직을 넣으면 리덕스이기 때문이다. 하지만 리덕스는 라이브러리를 제공하면서 편리한 기능들을 제공한다.

 

리덕스는 어떻게 동작할까?

- 컴포넌트는 dispatch() 함수를 통해 리듀서에 액션을 전달합니다. (여기서 액션이란 컴포넌트별 어떻게 서버와 핸들링 할지에 대한 것입니다!)

- 액션에는 리듀서에 처리해야 할 작업의 이름과 데이터가 객체 형태로 전달.

- 리듀서는 액션을 받아 스토어 변경 작업 실행

- 스토어 변경 작업이 완료되면 connect() 함수로 연결된 컴포넌트에 변경된 스토어의 데이터를 전파하여 동기화.

 

자 이제 적용 들어가보자. 딱 대~

노드 패키지를 추가합니다.

npm install redux react-redux
npm install redux-devtools-extension --dev

 

먼저 리덕스에서는 스토어를 생성하는 함수가 존재합니다.

createStore(reducer, /* 최초 상태 */, /* enhancer */)

저는 현재 사용하고 있는 프로젝트에서 legacy에 새로운 콤퍼넌트를 만들어서 실행해보겠습니다.

 

.src.legacy.ReduxTest.jsx 

import React, { PureComponent } from "react";
import { createStore } from 'redux';
import { Provider } from 'react-redux';

const ReduxTest = ()=> {
    const store = createStore(state => state);
    return (
        <Provider store={store}>
            리덕스 예제
        </Provider>
    );
}

export default ReduxTest;

 

PLUS++

리덕스 개발에 유용한 크롬 개발 툴입니다. 실습을 진행하면서 store 상태를 확인하기 위해서 사용합니다.

https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd/related?hl=ko 

 

Redux DevTools

Redux DevTools for debugging application's state changes.

chrome.google.com

데브틀을 연동하기 위해서는 다음과 같이 createStore 함수에 데브툴을 사용하겠다고 명시해줍니다.

 

...

const store = createStore(state => state, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

리덕스 스토어를 선언하였습니다~

자이제 안에 값을 넣어봐야겠죠?? 저는 로그인 상태를 관리할 수 있는 스토어가 필요합니다 .그에 맞는 변수를 설정해서 넣어볼게요.

 

const ReduxTest = ()=> {
    const store = createStore(state => state, { loading: false, name: 'redbin'}, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
    return (
        <Provider store={store}>
            리덕스 예제
        </Provider>
    );
}

 

저장완료~

 

자이젠 createStore를 이용하는 방법도 알았고 , 툴을 이용해서 쉽게도 확인 할 수 잇습니다. 포스팅 맨위에 있는 플로우처럼 Redux를 이용해야합니다. 적용시켜켜보기전에 액션, 리듀서, 디스페치의 각각의 기능을 알아야 구현할 수 있을것 같아서 간단한 예시로 알아보겠습니다.

 

액션과 리듀서?

액션은 다음과 같이 {type: .., payload: ...} 로 이루어진 객체입니다. type은 액션이 어떤 작업인지 쉽게 이해할 수 잇는 고유한 값 (ex 로그인 액션, 모달 액션... ) , payload === store값이라 보면 되겠습니다. 즉 액션에 어떤 이벤트를 실행할지와 어떤 값을 저장할지를 전달해준다고 보면 되겠죠 ㅎㅎ (payload는 생략가능..)

{
    type: 'SET_LOGIN',
    payload: true,
}

 

리듀서는 다음과 같은 구조를 가집니다.

function reducer(state, action) {return state; }

 

리듀서는 스토어 이전데이터 + 액션을 받아 새로운 스토어의 데이터를 생성해야하는데 다음과 같이 변경합니다.

이렇게하면 리듀서는 이전데이터의 상태에서 새로운 상태를  추가하게 됩니다. 💪💪💪💪💪💪

const reducer = (state, action) => state + action.payload;

 

이러한 액션은 dispatch() 함수를 통해서 리듀서로 전달됩니다.

 

함수형 컴포넌트를 사용하다보니 구조를 약간 변경하였습니다 .다음과 같은 소스코드를 실행하면 INIT함수가 실행된후 SET_LOADING이라는 액션 타입이 등록된것을 확인 할 수 가있습니다.

React.Hook 의 useDispatch는 상위 Provider 컴포넌트를 통해 전달된 store를 변경합니다. 따라서 상위에 Provider를 주게하고 내부에서 useDispatch를 사용하도록 변경하였습니다. 실제로 적용할 때는 index.js에서 Provider를 전달하는 형식으로 구현하면 될것 같습니다.

 

import React, { useState, useEffect } from "react";
import { createStore } from 'redux';
import { Provider, useDispatch} from 'react-redux';

const ReduxTest = ()=> {
    const store = createStore(state => state, { loading: false, name: 'redbin'}, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

    return (
        <Provider store={store}>
            <Example />
        </Provider>
    );
}

const Example = ()=> {
    const dispatch = useDispatch();

    useEffect(() => {
        dispatch({type: 'SET_LOADING', payload: true,})
    },[]);
    return ('리덕스 예제');
}

export default ReduxTest;

 

SET LOADING 함수가 전달된것을 보입니다.

이제 액션에 따라서 리듀서가 스토어에 저장해주는 함수가 필요하겠죠??? switch Case문의로 구현합니다.

 

const reducer = (state, action) => {
    const {type, payload} = action;
    switch(type) {
        case 'SET_LOADING' : {
            return {
                ...state,
                loading: payload,
            };
        }
        default:
            return state;
    }
}

 

그리고 createStore에 들어가는 리듀서 타입을 적용합니다. 이렇게되면 초기값을 그대로 state => staet 하는 함수가 아니라 들어온 타입에 따라서 데이터를 업데이트해주겠죠??

 

const store = createStore(reducer, { loading: false, name: 'redbin'}, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

 

사용법도 알았으니...

기본적인 사용방법을 알았으니 이제 redux라는 폴터를 만들어서  actions , reducers 로 관리해 보겠습니다.

위에 내용을 파일별 패키지별로 분류하는 작업입니다. 아래는

 

뭐 저는 이런식으로 분류하는데 자기 취향에 맞추어서 분류하면 됩니다. (근데 이러한 리덕스 파일을 분류하는 방법에 대해서도 구글에서 많이 소개 되고 있으니 본인의 프로젝트에 맞게 사용하시면 좋을것 같습니다)

 

위에 switch 문으로 사용하였던 reducer를 파일로 옮기고 export로 뺍니다. 이때 주의할점은 reducer는 초기 state상태를 주어야하는데 이값을 설정합니다.

 

loadingReducer.js

const reducer = (state= {}, action) => {
    const {type, payload} = action;
    
    switch(type) {
        case 'SET_LOADING' : {
            return {
                ...state,
                loading: payload,
            };
        }
        default:
            return state;
    }
};

export default reducer;

 

index.js 파일은 reducers 모듈들을 관리하기 쉽도록 다음과 같이 export 합니다.

import loading from './loadingReducer';

export default {
    loading,
};

 

configureStore.js 라는 파일은 스토어의 설정파일들을 저장합니다. 여러 개의 리듀서는 combineReduceres() 함수로 묶어 createStore() 함수의 인자로 전달합니다. 앞에 사용했던 window.__REdUX_DEVTOOLS_EXTENSION__()함수는 redux내장함수 composeWithDevTools()로 대체했습니다.

 

configureStore.js

import { createStore, combineReducers } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import reducers from './reducers';

const configureStore = initStates => createStore(
    combineReducers(reducers),
    initStates,
    composeWithDevTools(),
);

export default configureStore;

 

이제 ReduxtTest.jsx 파일을 다시한번 만들어보겠습니다.

ReduxTest.jsx

import React, { useEffect } from "react";
import { Provider, useDispatch} from 'react-redux';
import configureStore from "../redux/configureStore";

const ReduxTest = ()=> {
    const store = configureStore({ loading: false});

    return (
        <Provider store={store}>
            <Example />
        </Provider>
    );
}

const Example = ()=> {
    const dispatch = useDispatch();

    useEffect(() => {
        dispatch({type: 'SET_LOADING', payload: true,})
    },[]);
    return ('리덕스 예제');
}

export default ReduxTest;

잘적용되는 것을 확인 할수 있네요..

이제 Redux의 스토어를 저장하는 방법을 알았으니 액션을 부여할 차례입니다.

(actions 파일이 없고 그냥 reducer 자체에서 값들을 다선언 할 수 는 있지만 느슨한 결합을 위해서 분리합니다.)

loadingActions.js

export const SET_LOADING = 'loading/SET_LOADING';

export const setLoading = loading => ({
    type: SET_LOADING,
    payload: loading,
});

이제 위의 변경사항을 reducer 와 App 컴포넌트에 적용해보겠습니다.

 

loadingReducer.js

import { SET_LOADING } from "../actions/loadingActions";

const reducer = (state= {}, action) => {
    const {type, payload} = action;
    
    switch(type) {
        case SET_LOADING : {
            return {
                ...state,
                loading: payload,
            };
        }
        default:
            return state;
    }
};

export default reducer;

 

ReduxTest.jsx

import React, { useEffect } from "react";
import { Provider, useDispatch} from 'react-redux';
import configureStore from "../redux/configureStore";
import { setLoading } from "../redux/actions/loadingActions";

const ReduxTest = ()=> {
    const store = configureStore({ loading: false});

    return (
        <Provider store={store}>
            <Example />
        </Provider>
    );
}

const Example = ()=> {
    const dispatch = useDispatch();

    useEffect(() => {
        dispatch(setLoading(true))
    },[]);
    return ('리덕스 예제');
}

export default ReduxTest;

 

이상으로 react에 redux를 적용해보았습니다. :)

 

출처 : 실리콘 벨리 개발방법으로 배우는 리액트 프로그래밍 , 리덕스 공식홈페이지

반응형