본문 바로가기
Front-End/Redux

Redux Basics with Code Examples (Redux 예제)

by SeanK 2021. 12. 3.

 

 

이전 포스팅에서 Redux에 대한 기본적인 개념과 탄생 배경에 대해 알아보았다. 

 

이번 포스팅에서는 예제코드를 통해 어떻게 코드를 짜는지 살펴보자. 

 

이와 관련해 상세히 정리해놓은 글이 있어 번역하여 올려본다. 

 

출처: https://www.freecodecamp.org/news/redux-for-beginners/

 

Redux for Beginners – Learn Redux Basics with Code Examples

Redux can be confusing for beginner React developers to understand. There are a lot of concepts you need to know to use it properly, like reducers, actions, store, pure functions, immutability, and much more. But every React developer should know the basic

www.freecodecamp.org

 

 

How to Get Started with Redux

 

아래 명령어를 터미널에 입력 해보자.

 

npx create-react-app redux-demo
여기서 npx는 로컬 머신에 npm패키지를 설치하지 않고 새로운 프로젝트를 생성하여 create-react-app npm 패키지를 사용할 수 있도록 해준다.  

 

프로젝트를 생성했다면, 이제 아래 코드를 입력한다. 

 

npm install redux@4.1.0

 

 

How to Create the Redux Store

 

Redux에서 어플리케이션에서 변화하는 데이터를 관리하고 추적하는데 Store를 사용한다. 

 

Store를 생성하기 위해 아래와 같은 createStore 함수를 불러와야 한다. 

import { createStore } from 'redux';

 

createStore 함수는 아래의 세가지 arguments를 받는다:

  • 첫번째 인자는 reducer라고 불리는 함수 (required)
  • 두번째 인자는 state의 초기값 (optional)
  • 세번째 인자는 미들웨어와 같은 enhancer (optional)

아래의 코드를 살펴보자. 

import { createStore } from 'redux';

const reducer = (state, action) => {
  console.log('reducer called');
  return state;
};

const store = createStore(reducer, 0);

 

위 코드에서 우선은 화살표 함수를 이용해서 reducer 함수를 정의 했다. 원한다면 일반 함수 정의식도 사용가능하다. 

reducer함수는 콘솔에 텍스트를 표시하고 state 값을 반환하고 있다. 

그리고나서 reducer 함수를 createStore 함수에 첫번째 인자로 넘겨주고 초기값으로 0을 전달한다. 

createStore 함수는 어플리케이션 데이터를 관리할 수 있는 store를 반환한다. 

reducer함수는 파라미터로 state와 action을 받는다. 

createStore에서 0으로 전달하고 있는 초기값은 자동적으로 state 파라미터에 전달된다. 

하지만, 아래와 같이 reducer함수 내에서 state를 초기화하는 것이 더 일반적이다.  

 

import { createStore } from 'redux';

const reducer = (state = 0, action) => {
  console.log('reducer called');
  return state;
};

const store = createStore(reducer);

 

위처럼 파라미터내에서 초기값을 설정하는 것은 ES6문법을 이용한 것이다. 

store가 생성되면, store에서 제공하는 subscribe 메소드를 이용할 수 있는데, 이는 store에서의 변화를 'subscribe'한다. 

 

store.subscribe(() => {
  console.log('current state', store.getState());
});

 

여기서 subscribe함수를 사용함으로서, 스토어가 변경될 때마다 호출되는 콜백함수를 설정해 놓을 수 있다. 

콜백함수안에는 store.getState 메소드를 호출하여 현재의 state 값을 얻을 수 있다. 

 

따라서 정리하자면 아래와 같이 코드가 이루어진다. 

 

import { createStore } from 'redux';

const reducer = (state = 0, action) => {
  console.log('reducer called');
  return state;
};

const store = createStore(reducer);

store.subscribe(() => {
  console.log('current state', store.getState());
});

 

만약 npm start 명령을 통해서 어플리케이션을 실행시키면, 콘솔창에서 reducer called 메세지를 확인할 수 있다. 

실행즉시 메세지를 확인 할 수 있는 이유는, reducer함수는 createStore함수에 전달되는 즉시 호출되기 때문이다.

 

How to Change the Store

 

위와 같이 store 생성을 마쳤다. 하지만 지금으로서는 store를 사용할 수 있는 방법이 딱히 없다. 왜냐하면 sotre는 reducer함수를 이용해 연결되어 있지만 reducer안에는 store를 관리할 어떠한 코드도 들어있지 않기 때문이다. 

 

스토어를 변경할 수 있는 유일한 방법은 Action을 dispatching하는 방법이다. 

 

action은 store에게 아래와 같이 전달되는 객체를 의미한다. 

 

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

 

위 예문에서, dispatch 함수는 store 상에서 호출가능하며 action을 type INCREMENT를 store로 보내게 된다. 

dispatch 함수는 객체를 파라미터로 받고 이는 action이라고 불린다. 

action은 반드시 type 프로퍼티를 위와같이 가지고 있어야 한다. 만약 type 프로퍼티를 전달하지 않는다면 아래와 같은 에러메세지를 받게 될 것이다. 

It's a common practice and recommended to specify the type value in uppercase.

type은 ADD_USER, DELETE_RECORD, GET_USERS 등 실행하고픈 어떤 오퍼레이션이라도 상관없다. 

만약, 단어가 여러개라면 아래처럼 언더스코어로 나누어도 상관없다.

{ type: 'INCREAMENT_NUMBER' }

 

지금까지의 코드를 정리하면 아래와 같다. 

 

import { createStore } from 'redux';

const reducer = (state = 0, action) => {
  if (action.type === 'INCREMENT') {
    return state + 1;
  } else if (action.type === 'DECREMENT') {
    return state - 1;
  }

  return state;
};

const store = createStore(reducer);

store.subscribe(() => {
  console.log('current state', store.getState());
});

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

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

store.dispatch({
  type: 'DECREMENT'
});

 

이제 npm start를 통해 어플리케이션을 실행시키면 콘솔창에 current state 값이 변경되는 것을 확인 할 수 있다. 

 

찬찬히 어떻게 이런 과정을 거치게 되는지 한번 살펴보자.

 

위 코드에서 reducer함수는 아래와 같은 형태를 하고 있다. 

 

const reducer = (state = 0, action) => {
  if (action.type === 'INCREMENT') {
    return state + 1;
  } else if (action.type === 'DECREMENT') {
    return state - 1;
  }

  return state;
};

 

store.dispatch 함수를 호출하면, reducer 함수가 호출되게 되는데,

이때의 reducer 반환값이 바로 store의 새로운 값이 되는 것이다. 

 

따라서 아래와 같이 action을 store로 dispatch하면

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

 

reducer함수의 첫 번째 if 조건문이 실행 될 것이다. 이게 state 값을 초기값 0에서 1 증가시키게 된다. 그리고 증가된 값이 reducer 함수를 통해 반환된다. 

 

중요한 점은 새로운 값을 계산하기 위해 state를 사용한다는 것이지, state값을 아래처럼 변형하는게 아니다. 

if (action.type === 'INCREMENT') {
   state = state + 1;
   return state;
} 

reducer는 원본 state를 변형해서는 안되기 때문에 위의 코드는 옳지 않다. 위처럼 state에 직접 변형을 가하게 되면 어플리케이션에서 문제를 발생시킬것이기 때문에 추천하지 않는다. 

 

여튼 위와 같은 방식으로 reducer로 state를 수정하였다. 

이는 작성해놓은 store.subscribe 함수로 인해서 콘솔창에 업데이트된 state가 표시되게 된다. 

 

그리고 다시한번 INCREAMENT type으로 dispatch가 호출되면, 다시 동일한 절차가 반복되고 원래 1이었던 state값에 1이 더해져 2가 된다. 

 

그리고 아래와 같이 DECREMENT action을 store로 dispatch하게 된다. 

store.dispatch({
  type: 'DECREMENT'
});

이는 else 조건문을 실행시키게 되고 state를 2에서 1로 감소시킨다. 

 

여기서 중요한 점은 마지막으로 state를 반환하고 있기 때문에, 어떤 조건문에서 부합하지 않을 시 디폴트 state값이 반환되게 된다. 

 

보통은 if-else 조건문보다 case 조건문을 사용한다. 

const reducer = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
};

 

추가로, action을 전달할 때 type 뿐만 아니라 추가적인 정보도 함께 전달 할 수 있다. 

 

아래의 코드를 한번 살펴보자. 

 

import { createStore } from 'redux';

const reducer = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + action.payload;
    case 'DECREMENT':
      return state - action.payload;
    default:
      return state;
  }
};

const store = createStore(reducer);

store.subscribe(() => {
  console.log('current state', store.getState());
});

store.dispatch({
  type: 'INCREMENT',
  payload: 1
});

store.dispatch({
  type: 'INCREMENT',
  payload: 5
});

store.dispatch({
  type: 'DECREMENT',
  payload: 2
});

 

위 코드에서, action을 store로 dispatching 할 때, reducer 내부의 state 증감에 이용하는 값 payload를 함께 전달하고 있다. 

예제에서는 payload라고 프로머티명을 설정했지만, 이름은 아무것이나 상관없다. 

 

과정을 살펴보자.

 

reducer 함수는 아래와 같으며,

const reducer = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + action.payload;
    case 'DECREMENT':
      return state - action.payload;
    default:
      return state;
  }
};

 

INCREAMENT type action을 dispatch하는 코드는 아래와 같다. 

store.dispatch({
  type: 'INCREMENT',
  payload: 1
});

store.dispatch({
  type: 'INCREMENT',
  payload: 5
});

 

reduver에서는 아래와 같은 코드가 실행된다. 

return state + action.payload;

 

따라서 위의 코드는 이전의 state값에 1과 5를 더하게 되어 6이 된다. 

 

그리고 아래의 DECREAMENT action type으로 인해

store.dispatch({
  type: 'DECREMENT',
  payload: 2
});

 

store값은 최종적으로 4가 된다. 

 

 

 

 

 

 

'Front-End > Redux' 카테고리의 다른 글

[Redux] Redux Persist VS Local storage  (0) 2022.04.15
[Redux] Redux의 세 가지 원칙  (0) 2021.12.06
Redux WHAT IS REDUX?  (0) 2021.12.02