Dev/SW Engineering

26. Redux - Redux의 작동방식1. State, Store, Reducer, Action

HJChung 2020. 7. 28. 12:03

+ Redux에 대해 공부한 것을 정리한 것입니다. 배우는 중이라 잘못된  내용이 있을 수 있으며 계속해서 보완해 나갈 것입니다. :))

 

앞서서 Redux에서 사용하는 Action, Store 그리고 Reducer의 의미와 특징을 살펴보았다. 

여기서는 Redux을 배우고, 활용할 때 등장하는 State, Store, reducer,action등의 역할과 서로 어떻게 연관되어 작동하게 되는지를 정리해보고자 한다. 

 

1. State(상태)

React를 배울 때 처음 접한 State는 컴포넌트 내부적을 사용하는 데이터의 집합이다. 유튜브라면, 상태는 재생되고 있는 비디오 정보, 화면 비디오 리스트에 출력되는 비디오들의 정보, 다크모드 여부, 등이 될 수 있다.

앞선 유튜브 클론 코딩을 통해 state는 어떤 event나 API등을 통해 setState 메서드를 사용하여 바뀔 수 있는 것을 배웠다. 그리고 앱은 state를 읽어와서 화면에 그에 맞는 UI를 출력하게 된다. 

 

그런데 앞에서도 살펴보았듯이 React와 React에서의 state사용방식 만으로는 '너무 많은 props를 내려야 하는 경우'와 '너무 깊은 곳에 props를 전달해야 하는 경우' 가 생길테고, 

그러면

" 어떻게 이 문제를 해결할 수 있을까? 컴포넌트가 접근 할 수 있는 공용스토어(Store)가 있다면? 그럼 하위 컴포넌트든 상위 컴포넌트든 상관없이 각 컴포넌트가 자신이 필요할 때 스토어에 접근해서 가져오면 되지 않을까?

그리고 state 변경이 이루어질 수 있게 하는 것(Action) 과 state 변경을 해주는 Action을 스토어까지 가져와주는 스토어 관리자(Dispatcher)가 스토어의 상태 변경을 관리하면 불필요한 혼란이 줄지 않을까? "

 

이러한 역할을 해주는 것이 Redux이고, store, reducer, dispatch, subscribe, getState, action등의 개념이 등장한다. 

 

2. Store

Store는 state의 관리를 하는 전용 장소로,

아래처럼 state들이 store에 객체형식으로 저장된다. 

{
    type: "ORDER", 
    drink: {
    	menu: "Americano",
        size: "Grande",
        iced: false
        }
}
 

또한 규모가 클 경우에는 아래처럼 state를 카테고리별로 분류하는 경우가 일반적이라고 한다. (reference)

{
    // 세션과 관련된 것
    session: {
        loggedIn: true,
        user: {
            id: "114514",
            screenName: "@mpyw",
        },
    },
​
    // 표시중인 타임라인에 관련된 것
    timeline: {
        type: "home",
        statuses: [
            {id: 1, screenName: "@mpyw", text: "hello"},
            {id: 2, screenName: "@mpyw", text: "bye"},
        ],
    },
​
    // 알림과 관련된 것
    notification: [],
}

출처: 아마 이게 제일 이해하기 쉬울걸요? React+Redux 플로우의 이해 by carrot useless

2-1. Store의 생성

 //1. store을 만든다.  - Redux의 createStore API를 사용하고, createStore의 입력값은 Reducer함수이다. 
 // import { createStore } from 'redux'
 
 let store = Redux.createStore(reducer);

2-2. Store의 예

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'

import App from './containers/App'
import reducers from './reducers'

const store = createStore(
  reducers,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
)

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('app'),
)

2-3. Store의 주요 메서드

  • createStore(reducer)로 Redux store를 생성할 수 있다.
  • getState()를 통해 현재 state를 가져올 수 있다.
  • dispatch(action)을 사용하여, store의 reducer에 action을 전달한다. redux에서 상태 변경을 일으킬 수 있는 유일한 방법이다.

3. Reducer

Action은 뒤에서 다루겠지만 간단히만 말하자면 Store에 대해 뭔가를 하고싶거나 store의 state를 업데이트하고 싶을 때 발생하는 이벤트 드리븐같은 것이다. 이 Action은 Reducer로 전달된다.

Action을 전달받는 Reducer는 각 Action이 Store를 어떻게 업데이트할지를 기술하는 순수 함수이다. tore와 정보를 주고받는 역할을 한다. 업데이트 방식은 store의 state를 변형(muatate)하는 것이 아닌 '이전 state와 Action을 참고하여 새로운 state를 만들어서 반환하는 방식이다 . 

출처: codestates

3-1.  Reducer의 정의

항상 state와 action을 받아서 state를 return 해주어야한다.

 //createStore의 입력값으로 넣어주는 reducer는 action값과 기존의 state값을 참조해서 새로운 state를 만든다.
 function reducer([기존의 state], [action]){
    if([기존의 state]값 === undefined){ //아직 해당 state가 정의되지 않았으면
    	return({[초기 state값 key]: [value]}//초기값으로 초기화를 해줘야함
    }
 }

3-2. Reducer의 예

import { SET_USERNAME, SET_DARK_MODE } from '../actions/index'

const initialState = {
  currentUser: { name: 'Hyeonjeong' }, 
  darkMode: false,
}

const settingReducer = (state = initialState, action) => {
  let newState
  switch (action.type) {
    case SET_USERNAME:
      newState = Object.assign({}, state, { currentUser: { name: action.name } })
      break
    case SET_DARK_MODE:
      newState = Object.assign({}, state, { darkMode: action.value })
      break
    default:
      return state
  }
  return newState
}

export default settingReducer

3-3. Reducer의 예에서 보이는 'Object.assign'은  왜 쓰고, 어떻게 작동하는가

Reducer는 이전 state와 Action을 참고하여 새로운 state를 만들어서 반환하는 방식이다. 그래서 원본 state 객체를 직접 변경하지 않고, 객체에 새 값을 적용하기 위해 Object.asssign을 사용한다. 

Object.assign(대상 객체(target), 하나 이상의 출처 객체(sources))

Object.assign() 메소드는 열거할 수 있는 하나 이상의 출처 객체로부터 대상 객체로 속성을 복사할 때 사용한다. 대상 객체를 반환합니다.

이때 주목할 특징은 '동일한 키가 존재할 경우 대상 객체의 속성은 출처 객체의 속성으로 덮어쓰여진다'는 것이다. 

그래서 

Object.assign({}, {currentUser:{name: 'Hyeonjeong'}}, {currentUser: {name: action.name}}) 이면 currentUser가 동일한 키이기 때문에 {name: 'Hyeonjeong'}자리에 {name: action.name}이 새로 덮어 씌워진 'currentUser: {name: action.name}'인 새state가 생성되는 것이다.

 

3-4. state객체가 복잡하게 짜여진 경우(수정하고 싶은 state가 아주 깊은 구조로 되어 있는 경우) 그 안의 내용이 바뀐 새로운 state를 어떻게 만들 수 있을까

currentUser: {
  name: "김코딩",
  company: {
    name: "Coggle",
    department: "Development",
    role: {
      name: "Software Engineer",
    }
  }
}
//에서 "Software Engineer"라는 값을 바꾸는 좋은 방법?

이런 상황을 위해 좀더 쉽게 해줄수 있는 Library 가 많이 들어봤을 Immutable.js 이다.([출처] 13. react-tutorial 고급 (불변성 - Immutability 개념)|작성자 JeromeBaek)

이와 관련된 내용은 

https://ko.redux.js.org/recipes/structuring-reducers/immutable-update-patterns/

 

불변 업데이트 패턴 | Redux

리듀서 구조 잡기 > 불변 업데이트 패턴: How to correctly update state immutably, with examples of common mistakes

ko.redux.js.org

https://blog.naver.com/backsajang420/221358585344

 

14. react-tutorial 고급(Immutable.js 개념 및 주 사용 규칙)

14. react-tutorial 고급 (Immutable.js 개념 및 주 사용 규칙)# 시작에 앞서..이전 포스트에서 React 의...

blog.naver.com

을 참고하여서 더 공부가 필요하다. 

 

3-5. Reducer 주요 메서드 

combineReducers(reducer1, reducer2, ...): 여러 개의 reducer들을 하나로 합칠 수 있다.

 

4. Action

Action은 상태 변경을 일으키는 이벤트에 대한 정적인 정보이다. Reducer가 Action과 이전 state을 참고해서 새로운 state를 만들기 때문에 Action은 reducer가 구분 할 수 있도록 액션의 이름(타입) 과 데이터를 가진 객체형식이다. 

3-1.  Action의 정의

{
    type: [액션의 종류를 식별할 수 있는 문자열 혹은 심볼],
    [액션의 실행에 필요한 임의의 데이터],
}

3-2. Action의 예(Reducer와 action을 이용해서 새로운 state값 만들기)

export const SET_USERNAME = 'SET_USERNAME'
export const SET_DARK_MODE = 'SET_DARK_MODE'
export const SET_CURRENT_VIDEO = 'SET_CURRENT_VIDEO'

export const setUsername = (name) => ({
  type: SET_USERNAME,
  name,
})

export const setDarkMode = (value) => ({
  type: SET_DARK_MODE,
  value,
})

export const setCurrentVideo = (video) => ({
  type: SET_CURRENT_VIDEO,
  video,
})