pebblepark
개발계발
pebblepark
전체 방문자
오늘
어제
  • 분류 전체보기 (24)
    • Frontend (7)
    • Backend (7)
    • 인프라 (1)
    • CS (0)
      • Design Pattern (0)
    • 정리용 (9)
    • 회고 (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • springboot
  • 호이스팅
  • 리액트쿼리
  • ERR_UNSAFE_PORT
  • CORS
  • javascript
  • 스프링
  • SpringMVC
  • Git
  • Github Pages
  • wsl
  • debounce
  • react
  • useLayoutEffect
  • typescript
  • 스프링 의존관계
  • github
  • 스프링 빈
  • hoisting
  • vite
  • react-query
  • @ModelAttribute
  • TDZ
  • 무한스크롤
  • redux
  • Docker
  • Context API
  • React Query
  • 스프링 컨테이너
  • spring

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
pebblepark

개발계발

[Redux] 간단한 예제로 살펴보는 리덕스의 동작 원리
Frontend

[Redux] 간단한 예제로 살펴보는 리덕스의 동작 원리

2022. 3. 17. 23:24

React는 상태를 기반으로 사용자 인터페이스(UI)를 제어하기 위한 자바스크립트 라이브러리이다. 웹 애플리케이션은 사용자와의 활발한 인터렉션을 처리하고, 실시간 통신을 통해 데이터를 받아오며 이를 기반으로 렌더링을 하는 등 다양한 기능을 가지고 있다. 이때, 상태(state)를 관리하는 것은 필수적으로 요구되는 사항이다.

즉, 상태 관리란 React 앱 구축에 있어 핵심이 되는 부분 중 하나라 할 수 있다. 따라서 상태 관리를 위한 많은 기술이 다양한 라이브러리의 등장으로 이어지게 되었다. 그 중에서도 리덕스(Redux)는 리액트와 가장 오랜 기간을 함께했으며 가장 널리 알려진 상태관리 라이브러리 중 하나이다. 이번 포스팅에서는 리덕스의 동작 원리를 간단한 예제와 함께 살펴볼 것이다.

 

👉 리덕스의 코어 : createStore

약 25줄의 코드이지만 리덕스의 핵심 기능이 포함되어 있는 createStore 함수를 살펴보자.

createStore에서는 현재 상태 값과 여러 구독자를 추적하고, 값을 업데이트하고, 작업이 전달될 때 구독자에게 알리고, 상태 값을 반환 받는 API를 가지고 있다.

function createStore(reducer) {
    var state;
    var listeners = [];

    function getState() {
        return state;
    }

    function subscribe(listener) {
        listeners.push(listener);
        return function unsubscribe() {
            var index = listeners.indexOf(listener);
            listeners.splice(index, 1);
        }
    }

    function dispatch(action) {
        state = reducer(state, action);
        listeners.forEach(listener => listener());
    }

    dispatch({});

    return { dispatch, subscribe, getState };
}

 

👉 하나씩 살펴보는 Redux Core

먼저, 스토어는 다음과 같은 값을 가지고 있다.

  • 저장소에 저장할 상태 값 : state
  • 상태 값을 구독하고 있는(변경을 감지) 구독자들을 저장하는 목록 : listeners 배열
    • 해당 listner 들은 () => {} 형식의 콜백 함수
    • dispatch 함수가 호출될 때마다 실행된다.

모든 상태 값은 하나의 저장소(store) 안에 있는 객체 트리에 저장된다. 상태 트리를 변경하는 유일한 방법은 무엇이 일어날지 서술하는 객체인 액션(action)을 보내는 것 뿐이다. 액션이 상태 트리를 어떻게 변경할지 명시하기 위해 사용자는 리듀서(reducer)를 작성해야 한다.

function createStore(reducer) {
    var state;
    var listeners = [];
    // ...
}

getState()

getState 함수는 리덕스 저장소에 저장된 상태 값 state를 반환한다.

function getState() {
    return state;
}

subscribe(listener)

subscribe 함수는 상태값이 변경되었을 때 실행 될 콜백함수 listner 를 받아서 listners 배열에 추가한다. 해당 listner를 listners 배열에서 삭제하는 구독 해지 함수 unscribe 를 반환한다.

function subscribe(listener) {
    listeners.push(listener);
    return function unsubscribe() {
        var index = listeners.indexOf(listener);
        listeners.splice(index, 1);
    }
}

dispatch(action)

dispatch 함수는 action을 받아서 reducer 함수를 통해 state 를 변경한다. 이후 listners 배열의 listner 를 하나씩 호출하면서 상태가 변경되었음을 알린다.

function dispatch(action) {
    state = reducer(state, action);
    listeners.forEach(listener => listener());
}

반환값

createStore 내부에서 dispatch({}) 를 호출함으로써 reducer 함수에서 action이 빈 객체로 주어졌을 경우, 반환하는 값으로 state 를 초기화해준다. 이후 위에서 설명한 dispatch, subscribe, getState 함수를 담은 객체를 반환한다.

해당 함수들은 모두 클로저 함수이다. 따라서, createStore에서 관리하는 값(state, listeners)은 외부에서 접근하지 못하며, 현재 상태를 기억하고 함수를 통해 상태가 변경되어도 최신 상태가 유지된다.

function createStore(reducer) {
    // ...

    dispatch({});

    return { dispatch, subscribe, getState };
}

 

👉 리덕스 기본 예제

리덕스의 동작 핵심 코드만 살펴보았을 때 이해가 잘 되지 않을 수도 있다. 따라서 아래 예제를 통해 다시 살펴보도록 하자.

import { createStore } from 'redux'

/**
 * 이것이 (state, action) => state 형태의 순수 함수인 리듀서입니다.
 * 리듀서는 액션이 어떻게 상태를 다음 상태로 변경하는지 서술합니다.
 *
 * 상태의 모양은 당신 마음대로입니다: 기본형(primitive)일수도, 배열일수도, 객체일수도,
 * 심지어 Immutable.js 자료구조일수도 있습니다.  오직 중요한 점은 상태 객체를 변경해서는 안되며,
 * 상태가 바뀐다면 새로운 객체를 반환해야 한다는 것입니다.
 *
 * 이 예제에서 우리는 `switch` 구문과 문자열을 썼지만,
 * 여러분의 프로젝트에 맞게
 * (함수 맵 같은) 다른 컨벤션을 따르셔도 좋습니다.
 */
function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

// 앱의 상태를 보관하는 Redux 저장소를 만듭니다.
// API로는 { subscribe, dispatch, getState }가 있습니다.
let store = createStore(counter)

// subscribe()를 이용해 상태 변화에 따라 UI가 변경되게 할 수 있습니다.
// 보통은 subscribe()를 직접 사용하기보다는 뷰 바인딩 라이브러리(예를 들어 React Redux)를 사용합니다.
// 하지만 현재 상태를 localStorage에 영속적으로 저장할 때도 편리합니다.

store.subscribe(() => console.log(store.getState())))

// 내부 상태를 변경하는 유일한 방법은 액션을 보내는 것뿐입니다.
// 액션은 직렬화할수도, 로깅할수도, 저장할수도 있으며 나중에 재실행할수도 있습니다.
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1

 

Reducer 함수 : counter

counter 함수는 createStore에 넘겨줄 reducer 함수이다.

action.type 의 값이 default 일 경우 state를 반환하고 있다. 따라서 createStore 내부에서 dispatch({}) 를 실행하면 state 의 값이 0 으로 초기화 될 것이다.

이후 dispatch({type: 'INCREMENT'})를 호출하면 state 값이 +1 될 것이고, dispatch({type: 'DECREMENT'}) 를 호출하면 state 값이 -1 될 것이다.

function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

Subscribe

store의 subscribe 메서드에 () => console.log(store.getState()) 의 함수(listner)를 넘겨주었다. 해당 함수는 앞으로 store 의 dispatch 가 호출될 때마다 콘솔에 변경된 state의 값을 출력할 것이다.

store.subscribe(()  => console.log(store.getState())))

따라서 아래와 같이 dispatch 함수를 호출하면 state 값이 초기값 0에서 +1 되고, 해당 값은 콘솔에 출력된다.

store.dispatch({ type: 'INCREMENT' })

 

참고

  • [FE] 리액트 상태관리 1부.
  • Redux 시작하기
  • Idiomatic Redux: The Tao of Redux, Part 1 - Implementation and Intent

 

 

 

저작자표시 (새창열림)

'Frontend' 카테고리의 다른 글

React-Query 정리  (0) 2022.08.05
[Javascript] Throttle 과 Debounce  (0) 2022.05.12
[React] ContextAPI & useContext Hook을 통한 Global State 값 관리하기  (0) 2022.03.11
[Javascript] 변수, 호이스팅, TDZ  (0) 2022.03.07
[Typescript] Advanced-Types & Utility-Types 정리  (0) 2022.03.02
    'Frontend' 카테고리의 다른 글
    • React-Query 정리
    • [Javascript] Throttle 과 Debounce
    • [React] ContextAPI & useContext Hook을 통한 Global State 값 관리하기
    • [Javascript] 변수, 호이스팅, TDZ
    pebblepark
    pebblepark
    프론트엔드 개발자입니다. 피드백은 언제나 환영입니다:)

    티스토리툴바