Front-end/깊게 파고들기

React-Reudx 정리

아지송아지 2022. 1. 27. 10:00

오늘은 React-Redux를 알아보겠습니다.

최대한 redux를 모르는 사람한테 설명한다는 가정하에 글을 작성하였습니다.

 

리덕스는 상태 관리 툴로 프로젝트 전역에서 사용할 수 있는 변수를 생성한다고 생각하시면 됩니다.

redux를 사용해야 하는 이유는 아래와 같습니다.

 

1. 값을 넘길 때 props로 연속해서 타고 들어갈 일이 없어집니다.

2. 프로젝트 규모가 커질수록 필요성이 커집니다.

3. 리덕스 값이 바뀔 때 사용하고 있는 컴포넌트가 자동적으로 리렌더링이 일어납니다. 

이외에도 비동기, 미들웨어에 관한 것이 있습니다.

 

저희의 목표를 1번에서 말한 props로 타고 들어갈 일 없이 전역적으로 변수를 관리가 가능하게끔 만드는 것으로 잡겠습니다.

 

 

이번 글에서 다루는 코드는 아래 깃에 모아두었습니다.

https://github.com/hellojaehyeok/React_Redux

 

GitHub - hellojaehyeok/React_Redux: react-redux 셋팅 및 사용방법 정리입니다.

react-redux 셋팅 및 사용방법 정리입니다. Contribute to hellojaehyeok/React_Redux development by creating an account on GitHub.

github.com


전체적인 리덕스의 구조는 아래와 같습니다.

  • store : 변수들을 저장하고 있는 저장소입니다.
  • view : 우리가 보는 화면을 말합니다.
  • action : store에 있는 값을 바꾸고 싶을 때 action을 활용합니다.
  • reducer : 이전의 상태와 바꾸고자 하는 action을 합쳐 새로운 값을 생성합니다.

이 개념은 앞으로 진행하는 데 있어 꼭 필요합니다.

플로우를 글로 다시 정리하겠습니다.

1. store에 있는 정보를 화면에 보여줍니다.

2. 사용자가 값을 변경했을 때  action이 일어납니다.

3. reducer에서는 기존에 있던 값과 action에 있는 데이터를 합쳐 새로운 값을 만들어줍니다.

4. reducer에서 만든 값은 store에 저장되고 화면단에서는 자동적으로 리렌더링 됩니다.

 

이제 유저 데이터를 제어하는 리덕스 코드를 직접 짜면서 설명드리겠습니다.

저는 기능별로 리덕스를 js파일을 따로 만들어 관리합니다.

하나씩 만들어 보겠습니다.

 

store.js

// store.js
import { createStore } from 'redux';
import combineReducer from './combineReducer';

export const store = createStore(combineReducer);

createStore()로 스토어를 만들어 줍니다.

공식 문서를 보면 알 수 있듯 인자에는 reducer를 넣고 reducer가 여러 개 있을 경우에는 combineReducers를 넣어야 합니다.

저는 프로젝트를 하다 보면 보통 여러 개를 만들기 때문에 처음부터 combineReducers를 넣었습니다.

* store는 하나만 만들어야 합니다!

 

// index.js
import { store } from './store/store';
import { Provider } from 'react-redux';

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

store를 사용하기 위해서는 index.js에서 <Provider>로 하위 컴포넌트를 감싸주어야 합니다.

 

 

combineReducer.js

// combineReducer.js
import { combineReducers } from 'redux';
import userData from './modules/userData';

export default combineReducers({
    userData,
})

combineReducers서로 다른 리듀싱 함수들을 값으로 가지는 객체를 받아서 createStore에 넘길 수 있는 하나의 리듀싱 함수로 바꿔줍니다.

 

위 문장에서 말하는 리듀싱 함수들은 userData.js에서 만들어줄 겁니다.

만약 유저 데이터 이외에 다른 값들을 리덕스로 제어하고 싶다면 js파일을 만든 후 combinReducers안에 추가로 넣으면 됩니다.

 

 

userData.js

// userData.js
// 액션 타입을 정의한다.
const MODIFY_USERDATA = 'userData/MODIFY_USERDATA';
const INIT_USERDATA = 'userData/INIT_USERDATA';

// 액션 생성함수를 만든다.
export const modifyUserData = (data) => ({data:data, type:MODIFY_USERDATA});
export const initUserData = () => ({type:INIT_USERDATA});

// 초기값 제작
const initialState = {
    name:"",
    job:"",
    email:"",
    phone:"",
}

/* 리듀서 선언 */
export default function userData(state = initialState, action) {
  
  switch (action.type) {
    case MODIFY_USERDATA:
      return {...action.data};
    case INIT_USERDATA:
      return initialState;
    default:
      return state;
    }
  }

이제 핵심 부분인 userData.js를 살펴보겠습니다.

리덕스를 처음 접하신다면 액션 타입, 생성 함수, 초기값이라는 단어가 있어 복잡해 보일 수 있습니다.

알고 보면 그렇게 어려운 것이 아닙니다.

 

처음부터 차근차근 보겠습니다.

 

const MODIFY_USERDATA = 'userData/MODIFY_USERDATA';

먼저 위에서 말했듯 store에 있는 값을 바꾸고 싶을 때 액션을 활용합니다.

이 액션이 어떠한 액션인지, 무엇을 하고 싶은지 즉 "액션의 타입"을 상단에서 지정해 줍니다.

 

export const modifyUserData = (data) => ({data:data, type:MODIFY_USERDATA});

타입을 지정했으면 액션 생성 함수를 제작하여 리듀서로 보낼 준비를 합니다.

원래는 dispatch라는 것을 사용하여 상태 변경을 일으킵니다.

하지만 저희는 나중에 나올 bindActionCreators를 사용할 것이기 때문에 일단은 "상태 변경을 위한 함수를 제작한다"라고 생각하시면 편하실 겁니다.

 

const initialState = {
    name:"",
    job:"",
    email:"",
    phone:"",
}

초기값을 생성합니다.

오브젝트 형태로 이루어져 있으면 말 그대로 데이터의 초기값을 지정해줍니다.

 

/* 리듀서 선언 */
export default function userData(state = initialState, action) {
  
  switch (action.type) {
    case MODIFY_USERDATA:
      return {...action.data};
    case INIT_USERDATA:
      return initialState;
    default:
      return state;
    }
  }

다음으로 리듀서를 선언해줍니다.

이 리듀서가 바로 combinReducers에서 하나로 합쳐집니다.

아까 저희가 만든 액션 생성 함수를 통해 이곳에 오게 되는데요.

state는 이전 값(데이터가 없으면 초기값) , action은 사용자가 어떠한 액션을 취했는지 정보가 들어옵니다.

 

switch문으로 액션의 타입이 어떤 것인지에 따라 분기 처리를 해주면 됩니다.

데이터 수정일 때 action.data에 수정할 데이터가 모두 들어있다고 가정한다면 바로 리턴해주면 됩니다.

초기화일 경우 초기값(initialState)을 리턴해줍니다.

리턴값은 리덕스의 정보를 바꾸어 줍니다.

 

 

actionCreators.js

import { bindActionCreators } from 'redux';
import * as userDataAction from './modules/userData';

import { store } from './store';

const {dispatch} = store;

// 각각의 생산자들을 dispatch로 감싸서 바로 호출 가능하게 만든 객체로 바꿔준다.
export const UserDataAction = bindActionCreators(userDataAction, dispatch);

마지막 js 파일입니다.

bindActionCreators각각의 생산자들을(userData에서 만들었던 액션 생성 함수) dispatch로 감싸서 바로 호출 가능하게 만든 객체로 바꿉니다.

 

저희는 이 파일을 통하여 userData의 리듀서로 갑니다.

 

이제 모든 준비는 끝났습니다.

활용하기 전에 정리를 한번 해보겠습니다.

1. 처음에 저희는 데이터를 모두 담는 store를 만들었습니다.

2. 그 다음 리듀서(store 정보들을 바꾸는 역할)들을 한 곳에 모을 combineReducers를 만들었습니다.

3.  액션 타입, 액션 함수, 리듀서를 각각 useData.js에 만들었습니다.

4. actionCreators.js에서 bindActionCreators를 통해 dispatch를 바로 호출합니다.

5. 리듀서는 이전 데이터와 새로운 데이터/타입을 활용해서 새로운 값을 만들어 줍니다.

 

 

활용!

데이터 가져오기

import { useSelector } from 'react-redux';
...
const userData = useSelector(store=>store.userData)

useSelector()를 활용하여 store에 접근합니다.

 

데이터 수정 & 초기화 

import { UserDataAction } from '../store/actionCreators';
...
const onClickSave = () => {
    UserDataAction.modifyUserData({
        name,
        job,
        email,
        phone,
    }) 
}

const onClickInit = () => {
    UserDataAction.initUserData()
}

수정을 하고 싶을 경우에는 actionCreator에서 이전에 만든 액션 함수를 통하여 데이터를 수정합니다.

초기화의 경우에도 UserDataAction.initUserData()를 실행하면 reducer에 있는 코드가 실행됩니다.

초기화 로직을 보면 아래와 같습니다.

1.  UserDataAction.initUserData() 실행

2. 리듀서에 접근

3. action의 타입을 비교하여 알맞은 코드 실행

4. initalState를 리턴하여 데이터를 초기화

 

 


 

여기까지입니다.

다 작성하고 보니 조금 어렵게 느껴지기도 하네요.

직접 실행해 보시면서 콘솔도 찍어보고 프로젝트에 넣어보면 금방 감을 익히실 겁니다.

 

수정/추가해야 할 부분 피드백 주시면 바로 반영하겠습니다.

긴 글 읽어주셔서 감사합니다!!