티스토리 뷰

✅ 사내에서 redux로 상태 관리를 하여 개발한 메뉴가 있는데, 당시 개발 기간이 짧아 정확하게 이해하지 못하고 구현하느라 개념잡기가 어려웠습니다. 따라서 이번 포스팅으로 공부해보고자 글을 작성합니다. ( 글을 읽기 전 redux 기본 용어에 대해 읽어보시면 이해하는데 도움이 될 것 같아서 더보기란에 짧게 정리하였습니다. )

 

더보기

 

액션(Action) : 상태에 변화가 필요하다면 액션을 일으켜야 합니다. 액션은 store에 있는 state를 변경하기 위해 보내는 신호로, 객체로 표현되고, type 필드는 반드시 가지고 있어야 합니다. 이를 바탕으로 리듀서에 작동하게 하는 것이 가능합니다. 액션 객체를 만들어주는 함수를 액션 생성 함수(Action Creator)라고 합니다.

function addTodo(data) {

 return {
   type: 'ADD_TODO',
   data,
 }
 
}

// addTodo : 액션 생성함수
// { type: 'ADD_TODO', data, } : 액션 객체

 

리듀서(reducer) : 리듀서는 action과 state를 파리미터로 받아 처리하는 함수입니다. 액션 유형을 기반으로 이벤트를 처리하는 이벤트 리스너라고 생각하면 됩니다. 주의할 점은 리듀서안에서 동적인 처리나 변화가 있어서는 안 됩니다.

const initialState = {
 todo: '',
 counter: 1,
}

function reducer(state = initialState, action) {

 switch (action.type) {
 
   case "ADD_TODO":
     return {
       ...initialState,
       todo : action.payload
     }
     
   default:
     return state
     
 }
 
}

 

 

스토어(Store) : 스토어는 전역으로 관리하는 state를 담는 곳입니다. 하나의 프로젝트는 하나의 스토어만 가질 수 있습니다.

 

디스패치(Dispatch) : 만들어진 액션을 리듀서에 보내 스토어의 변화를 만들어냅니다. 이벤트 트리거라고 생각하시면 됩니다. 

 

* Redux의 3 원칙 *

 

  • 1. Single source of truth
    • 앱 내 오직 하나의 스토어가 존재해야 합니다.
  • 2. State is read-only
    • state를 직접 변경하는 것은 안되며, 액션 & 디스패치를 통해 state를 변경해야 합니다.
    • 상태 업데이트 할 때 기존의 객체는 건들이지 않고 새로운 객체를 생성해야한다.
  • 3. Changes are made with pure functions.

    • 리듀서는 반드시 순수함수여야합니다.
       
      • 리듀서 함수는 이전 상태와 액션 객체를 파라미터로 받는다.
      • 파라미터 외의 값에는 의존하면 안된다.
      • 이전 상태는 절대 건들이지 않고 변화를 준 새로운 상태 객체를 만들어서 반환한다.
      • 똑같은 파라미터로 호출된 리듀서 함수는 언제나 똑같은 결과 값을 반환해야 한다.

📌 Redux

  • 애플리케이션의 state를 관리하기 위한 오픈소스 JS 라이브러리입니다.
  • 단방향 데이터 흐름(Flux 구조)을 가집니다.

이전 포스팅 글 zustand - 기초 참고.
왼쪽 : MVC 패턴에서의 데이터 흐름    오른쪽 : Flux 구조에서 데이터 흐름(redux)

 

이와 같이 Flux 구조는 View에서 Action을 넘겨주고, 그 Action은 Dispatcher를 거쳐 데이터 변경을 진행하고 Store에 값을 저장하는 과정을 통해 View는 최종적으로 변경된 데이터를 Store를 통해서 전달받습니다. (Redux의 데이터 흐름)

=> Redux = (Red)ucer + Fl(ux)

 

📌 이제 코드로 살펴보겠습니다.

// npm
npm install redux react-redux

// yarn
yarn add redux react-redux

1. redux 폴더를 만들고 action.js / rootReducer.js / store.js 파일을 만들어 줍니다.

 

더보기를 누르시면 소스코드를 확인하실 수 있습니다.

더보기
// action.js

export const setArr = arr => ({
    type: "SET_ARR",
    payload: arr,
});

/*
 arr이라는 데이터를 받아서 "SET_ARR"이라는 action을 반환하여 reducer에 전달해주는 액션 함수입니다.
/*
// rootReducer.js

const initialState = {
    arrData: [],
};

const rootReducer = (state = initialState, action) => {

    switch (action.type) {
        case "SET_ARR":
            return { ...state, arrData: action.payload };
        default:
            return state;
    }
};

export default rootReducer;

/*
state와 action처리 로직이 들어갑니다. state에는 초기값을 지정할 수 있습니다.(위 코드에서 initialState)
위 리듀서의 경우 "SET_ARR" 타입의 액션을 받으면 arrData라는 state를 action에서 받아오는 payload로 바꿔어줍니다.
*/
// store.js

import { createStore } from "redux";
import rootReducer from "./rootReducer";

const store = createStore(rootReducer);

export default store;

/*
createStore는 애플리케이션의 상태 트리 전체를 보관하는 Redux 저장소를 만듭니다.
첫번째 인자로 reducer를 받습니다. 여기서는 앞서 만든 rootReducer를 받습니다.
*/

 

 

2. 위에서 만든 Store를 적용시킵니다.

// 최상단의 index.js

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

import App from "./App";

const appRouting = (

    <Provider store={store}>

		<App />
        
    </Provider>
);

ReactDOM.render(appRouting, document.getElementById("root"));
Provider 라는 컴포넌트는 React 컴포넌트를 감싸줌으로써 하위 컴포넌트들이 redux의 store에 접근 가능하게 해 줍니다. 

 

 

3. 그럼 이제 redux를 사용해봅시다!

/* App.js 하위 컴포넌트를 정의하고 렌더링해봅시다.
App.js ex))

    const App = () => {
        return (
            <div class="App">
                <body className="App-body">
                    <Play3 />
                </body>
            </div>
        );
    };
    
*/

// App.js 하위 컴포넌트
import React, { useState, useEffect } from "react"

import { setArr } from "../../redux/action";
import { connect } from "react-redux";

const Play3 = (props) => {

    useEffect(() => {
        props.setArr_test([1, 2, 3]);
    }, []);

    return (
        <React.Fragment>

            <section>
                {props.arrData.map((dr) => {
                    return <div>{dr}</div>
                })}
            </section>

        </React.Fragment>
    )
}

const mapStateToProps = state => {
    return {
        arrData: state.arrData,
    };
};

const mapDispatchToProps = dispatch => {
    return {
        setArr_test: arr => dispatch(setArr(arr)),
    };
};

export default connect(mapStateToProps, mapDispatchToProps)(Play3);
mapStateToProps

store로부터 state를 가져와서 컴포넌트의 props로 state를 보내주는 역할을 합니다. ( return 한 객체 )
한 마디로, store에서 데이터를 가져와서 return 한 객체를 해당 컴포넌트의 props에 넣는다는 의미입니다. 

 

mapDispatchToProps

action을 reducer 함수에게 보내게 하는 역할을 하는 dispatch를 호출하는 함수를 정의하여 컴포넌트 props에 보낼 수 있습니다.
한 마디로, 컴포넌트 내에서 액션을 호출할 함수를 정의 합니다. (보통 액션명과 동일하게 하나, 저의 경우 이해하시기 쉽게 setArr이 아닌 setArr_test로 정의하고 호출했습니다.)

 

connect

Provider를 통해 store 접근 권한을 줬다면, state를 이용하기 위해서는 컴포넌트를 store에 연결해야 합니다. 이때 connect 함수를 사용합니다.

기본 구문 : connect(mapStateToProps, mapDispatchToProps)(컴포넌트명)

만약 mapStateToProps 만 사용하는 경우 connect(mapStateToProps) or connect(mapStateToProps,null)
만약 mapDispatchToProps 만 사용하는 경우 connect(null, mapDispatchToProps)

 

 

4. 결과 확인!

 

5. 마지막으로 데이터 흐름을 확인해보겠습니다.

 

useEffect 쪽 디버거
action.js 쪽 디버거
rootReducer.js 쪽 디버거
콘솔 결과

  • 디버거와 콘솔을 찍으면서 확인해봅시다.

    • mapDispatchToProps에서 정의했던 setArr_test 함수를 호출하면
    • setArr 액션 함수가 호출됩니다. setArr함수는 "SET_ARR"이라는 tpye을 reducer로 리턴하고
    • reducer에서는 리턴된 타입 "SET_ARR"에 맞는 state 변경 로직을 수행합니다.
    • 변경된 state는 mapStateToProps를 통해서 컴포넌트에 전달되고 우리는 최종적으로 4번의 결과 화면이 렌더링 된 것을 확인할 수 있습니다.

 


🚨 글 제목에서 알 수 있듯이 Redux의 기초입니다. api를 호출하여 사용하려면 redux-saga 와 같은 다른 라이브러리 또한 추가해야하며 데이터 처리 과정은 보다 더 복잡해지고, 더 많은 용어가 나옵니다. 그러므로 Redux 기초인 해당 글 부터 반드시 숙지를 하시기 바랍니다! 다음 글은 Redux 심화편 redux-saga 연동하기로 돌아오겠습니다~

 

 

 

🔗 참고한 글

 

React에서 Redux가 왜 필요할까?

안녕하세요! 휴몬랩 초보 개발자 수달입니다. :)  리액트를 이용한 웹을 개발하다 보면, State 관리에서 빈번한 오류 발생과 현재의 값 확인이 매우 복잡한 경우가 존재합니다. props와 state를 사용

devlog-h.tistory.com

 

[Redux] Redux 사용법 기초 useState처럼 사용하기

Redux 가장 간단하게 사용하는 방법!

velog.io

댓글