React(생활코딩)_20일차_ReactRedux(2)_Redux 도입

Lina Hongbi Ko·2023년 10월 16일
0

React_생활코딩

목록 보기
21/23

저번시간에 우리는 리덕스 없이 리액트 컴포넌트들을 중첩되게 만들어서 props와 이벤트로 상위 컴포넌트와 하위 컴포넌트를 컨트롤 해보았다.

이 과정을 통해 복잡하고 귀찮은 단계들을 거쳐 컴포넌트의 상태를 관리해야 한다는 것을 깨달았다.

그리고 이러한 상태들을 손쉽게 관리 해주기 위해서 우리는 'Redux'라는 것을 사용한다고 한다.

리덕스라는 개념 자체를 이 책을 읽기까지 몰랐기 때문에 개념을 이해하고 또 어떻게 사용하는지 정리해보려고 한다.

✏️ Redux 도입

📍 What is Redux?

리덕스 공식 홈페이지에 설명하길,

Redux는 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너입니다. Redux는 여러분이 일관적으로 동작하고, 서로 다른 환경(서버, 클라이언트, 네이티브)에서 작동하고, 테스트하기 쉬운 앱을 작성하도록 도와줍니다. Redux는 JavaScript로 만들어진 애플리케이션들의 state(상태)를 관리하기 위한 라이브러리입니다.

이라고 되어 있다.

쉽게 말해
Redux(리덕스)란 JavaScript(자바스트립트) 상태관리 라이브러리이고, 리덕스의 본질은 Node.js 모듈이다.

📍 Why Redux?

그럼 우리는 왜 리덕스를 사용할까?

이전에 실습한 경험을 바탕으로 우리는 일일이 컴포넌트들의 상태를 관리하는 것이 복잡하고 귀찮은 작업이라는 것을 알았다.

좀 더 설명하자면,

요로코롬 To do list 라는 어플리케이션을 만들었고, 이 앱을 구성하는 컴포넌트들을 도식화하는 그림을 보면

대부분의 작업에서 부모 컴포넌트가 중간자 역할을 해야 한다.

컴포넌트들이 소규모면 모를까, 대규모의 작업이라면 다루는 데이터와 함수 모두 유지보수 하는 것이 힘들어진다.

하지만 리덕스를 사용하면 상태값을 컴포넌트에 종속시키지 않고, 상태 관리를 컴포넌트의 바깥에서 관리 할 수 있게 된다.

생코님의 책을 보고 다시 한번 정리하자면,

'리액트'는 컴포넌트를 만들어서 체계적이고 잘 정돈된 어플리케이션을 만들게 해주는 기술이라면 '리덕스'는 상태를 중앙에서 관리함으로써 데이터가 우리가 예측하지 않은 형태로 변할 가능성을 낮춰주는 기술이라고 했다.

그리고 비유하자면 리액트는 '소문'과 같고 리덕스는 '미디어' 같은 느낌이라고 했다.

리액트를 컴포넌트로 만들어진 사회라고 생각했을때, 어떤 컴포넌트에서 변화가 생기면 그 변화를 표현하는 데이터가 발생하면 모든 컴포넌트로 그 데이터가 전파된다. 이것을 마치 소문과 비슷하다고 말했다. 그러면 그 데이터가 필요한 컴포넌트는 소문을 듣고 그것을 이용하면 되고 필요없는 컴포넌트는 이용하지 않으면 되지만, 모든 사람이 필요 없는 소문을 듣게 된다는 단점이 있다. 리액트의 관점에서 설명하자면, 렌더링이 계속 반복되어 앱의 성능이 떨어진다. 그리고 소문이 전파 되기 위해서는 집집마다 연결되어 있어야 하기 때문에 리액트의 컴포넌트들이 서로 props와 이벤트를 매개로 연결되어야 한다.

이런 소문은 작은 규모에서는 괜찮지만, 큰 규모에서 소문으로만 사회가 운영된다면 아주 비효율적일 것이다.

이 때 쓸 수 있는 도구가 리덕스이다.

리덕스는 언론에 비유할 수 있다. 모든 정보를 리덕스가 가지고 있고, 어떤 컴포넌트가 구성원들에게 전달하고 싶은 정보가 있으면 소문을 퍼뜨리는 대신 리덕스라는 언론사에 제보한다. 그러면 리덕스는 전체 컴포넌트에 방송을 한다. 또 어떤 컴포넌트가 전체에게 하고 싶은 말이 있으면 리덕스를 통해 스토어에 제보를 하면 된다.

그렇지만, 방송으로 모든 문제가 해결되지는 않는다. 방송은 필요하지 않는 사람에게도 그 소식이 전달된다는 비효율성을 여전히 가지고 있기 때문이다. 이때 리액트와 리덕스를 연결해주는 라이브러리인 react-redux를 사용하면 그 소식이 필요한 컴포넌트에만 전할 수 있다. 그 컴포넌트만 렌더링 된다는 얘기이다.



(이미지 출처: https://redux.js.org/tutorials/essentials/part-1-overview-concepts)


리덕스를 이용해 상태를 관리하는 법은 위의 그림과 같다.

처음 렌더링될때, UI 컴포넌트는 리덕스 스토어의 state에 접근해 해당 상태를 렌더링한다. 그리고나서 UI의 상태가 변경되면, 디스패치를 실행해 액션을 발생시킨다. 그리고 새로운 액션을 받은 스토어는 리듀서를 실행하고 리듀서를 통해 나온 값을 새로운 상태로 저장한 후, 업데이트된 상태의 데이터를 새롭게 렌더링한다.

좀 더 자세히 설명하자면,

리덕스를 프로젝트에 적용하면 '스토어'라는 아이가 생기고 이 스토어 안에는 프로젝트의 상태의 데이터들이 담겨 있다.

그림에서 보듯이, G 컴포넌트는 스토어를 구독(subscribe)하는데 이 과정에서 특정함수(listener)를 전달한다. 이 특정함수는 나중에 스토어의 상태값이 변할때 호출되는 함수이다.

B 컴포넌트에 어떤 이벤트가 생겨서 상태를 변화시킬 일이 생겼다. 이때 dispatch를 통해 스토어에 액션(action)을 던진다. 액션은 상태에 변화를 일으킬 때 참조할 수 있는 객체이고, 이 액션 객체는 필수적으로 type이라는 값을 가지고 있어야 한다. 예를 들어, {type: 'INCREMENT'} 라는 액션 객체를 스토어가 전달 받으면 이 액션의 이름을 보고 상태값을 변경해준다.

액션 객체를 받으면, 전달받은 액션의 타입(액션의 이름)에 따라 상태를 업데이트 해줘야하는데 이 작업을 하는 함수를 reducer 이라고 한다. 리듀서 함수는 두가지의 파라미터를 받는데,

  1. state: 현재상태,
  2. action: 액션객체

를 받는다. 이 두가지 파라미터를 참조해 새로운 상태 객체를 만들어서 반환한다.

그리고 이제 상태에 변화가 생기면, 이전에 컴포넌트가 스토어한테 구독할때 전달해줬던 특정함수(listener)가 호출된다.

이렇게해서 컴포넌트는 새로운 상태를 받고 리렌더링된다.

📍 Redux 개념 정리

1. Action

스토어의 상태를 변경하기 위해서 액션을 생성해야 한다. 액션은 객체이고 반드시 type 필드를 가져야 한다. 이 값을 액션의 이름이라고 생각하면 된다. 그리고 그 외의 값들은 나중에 상태 업데이트를 할 때 참고해야할 값이고, 마음대로 넣을 수 있다.

// 액션 객체 작성
{
	type: 'ADD_TODO',
    data: {
    		  id: 1,
              text: '리덕스 배우기',
    	  }
}

{
	type: 'CHANGE_INPUT',
    text: 'HI'
}

그리고 액션객체는 액션생성함수에 의해서 만들어진다. 변화를 일으킬때마다 액션객체를 전달해줘야하는데, 매번 액션객체를 작성하기는 번거롭기 때문에 함수로 만들어서 관리한다.

// 액션 생성 함수 작성
function addTodo(data) {
	return {
    	type: 'ADD_TODO',
        data
    };
}

// arrow function mode
const changeInput = text => ({
	type: 'CHANGE_INPUT',
    text
})

2. Reducer

리듀서는 현재 상태와 액션 객체를 받아 새로운 상태를 리턴하는 함수이다. 쉽게 말해, 변화를 일으키는 함수이다. 액션을 만들어서 발생시키면 리듀서가 현재 상태 & 전달받은 액션객체를 파라미터로 받아와서 새로운상태를 만들어 반환한다.

// reducer 함수

const initialState = {
	counter: 1
};

function reducer(state=initialState, action) {
	switch(action.type) {
    	case INCREMENT:
        		return {
                	counter: state.counter + 1
                };
        default:
        		return state;
    }
}

3. Store

리덕스를 적용하기 위해 스토어를 만드는데, 스토어는 컴포넌트의 상태를 관리하는 저장소이다. 하나의 프로젝트는 하나의 스토어만 가질 수 있다. 스토어 안에는 현재 앱의 상태와 리듀서가 들어가 있고, 그 외에도 몇가지 내장함수를 갖고 있다.

4. Dispatch

스토어의 내장 함수 중 하나이고, 액션 객체를 넘겨줘서 상태를 업데이트 시켜주는 역할을 한다.
액션을 발생시킨다고 이해하면 되고, 액션 객체를 파라미터로 넣어서 호출한다. 이 함수가 호출되면 스토어는 리듀서 함수를 실행시켜 새로운 상태를 만들어준다.

5. Subscribe

스토어의 내장 함수 중 하나이고, 파라미터안에 리스너함수를 넣어 호출해주면, 액션이 디스패치 되어 상태가 업데이트 될때마다 리스너 함수가 호출된다.

const listener = () => {
	console.log('update state");
}

const unsubscribe = store.subscribe(listener);
unsubscribe(); // 나중에 구독취소를 할 때 이 함수를 호출한다.

📍 Redux의 규칙

리덕스를 사용할때 지켜야하는 규칙.

1. 단일 스토어

  • 하나의 어플리케이션 안에는 하나의 스토어만 존재한다.
  • 하나의 스토어에 모든 상태가 관리되어야 한다.

2. 읽기 전용 상태

  • 리덕스 상태는 읽기 전용이므로, 액션 객체에 전달해야만 변화할 수 있다.

3. 순수함수로 작성

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

💡 순수함수에 대해,,

순수함수는 동일한 인자가 주어졌을 때 항상 동일한 결과를 반환해야 하며 외부의 상태를 변경하지 않는 함수이다. 그리고 함수 내 변수 외에 외부의 값을 참조, 의존하거나 변경하지 않아야 한다.

쉽게말해, 부수효과 (외부의 상태를 변경하는 것 또는 함수로 들어온 인자의 상태를 직접 변경하는 것)가 없는 함수이다.

function add(a,b){
    return a + b;
}
console.log( add(2,6) ); // 8

add는 순수함수다. 언제, 어디서 실행해도 add(2, 6)는 항상 8를 리턴하고, 외부 상태를 변경하지 않았기 때문이다.

let c = 5;
function add2(a,b){
    return a + b + c;
}
console.log(add2(10,3)); // 18
c = 20;
console.log(add2(10,3)); // 33

add2는 순수함수가 아니다. 함수 내에서 외부의 c라는 변수가 변하면 결과값도 달라지기 때문이다. 하지만 만약에 c가 상수라면 add2는 순수함수이다. 외부의 값을 참조해도 결과값이 같은 인자를 넣었을때 항상 같기 때문이다.

let c = 50;
function add3(a,b){
    c = b;
    return a + b;
}
console.log('c : ', c); // c : 50
console.log(add3(20,30)); // 50
console.log('c : ', c); // c : 30

add3는 순수함수가 아니다. 함수가 외부의 값을 변경하는 코드를 가지고 있기 때문인데, 리턴하는 값이 항상 일정하더라도 외부의 상태를 변경하는 코드가 있으면 순수함수가 아니다.

let obj1 = {val : 6};
function add4(obj, b){
    obj.val += b;
}

add4는 순수함수가 아니다. 객체를 인자로 받고, 그 상태를 변경시키는 코드를 가지고 있기 때문이다.

let obj1 = {val : 6};
function add5(obj, b){
    return { val: obj.val + b }
    // obj의 val만 참조만 할 뿐 변경없음.
}
console.log(obj1.val); // 6
let obj2 = add5(obj1,5);
console.log(obj1.val); // 6
console.log(obj2.val); // 11

add5는 순수함수이다. add4와 달리 객체를 순수함수로 나타내려면 객체의 값을 참조만 한다. 순수함수로 만들고, 객체값(obj1.val)의 변경이 필요하면 외부에서 객체가 값을 적용하는 방식 (obj2)로 사용한다.

순수함수에 대해서 잠깐 알아봤으면,

왜 Redux Reducer은 순수함수여야만 하는지 궁금해졌다.
그리고 찾아보니,,

💡💡 왜 Redux Reducer은 순수함수이어야만 하는가?

리덕스를 다룰때 주의해야할 점으로 Reducer은 반드시 순수함수여야만 한다. 그 이유는 리덕스의 state는 불변해야 한다는 특징을 가지고 있다. 그렇다고 state가 변경되면 안되는 것이 아니다. state가 수정되면 안된다는 뜻이다.

function counter(state = initialState, action) {
	switch(action.type) {
		case types.INCREMENT:
			return { ...state, number: state.number + 1 };
		case types.DECREMENT:
			return { ...state, number: state.number - 1 };
		default:
			return state;
	}
}

위의 코드를 보면 action의 타입에 따라 숫자를 증감시키는 counter reducer이라는 것을 알 수 있다. state의 값을 변경할때 spread 연산자로 기존 state 값을 복사한 후, number이라는 옵션만 변경해 state를 리턴해주는 것을 볼 수 있다.

state를 직접 수정하지 않고 복사본을 만들어서 수정하는 이유는 리덕스의 '변경 감지 알고리즘' 때문이다.

리덕스는 reducer를 거친 state가 변경됐는지 검사하기 위해 state 객체의 주소를 비교한다. 이때 복제본을 만들어 반환하면 이전의 state와 다른 주소값을 가지기 때문에 state가 변경되었다고 생각한다. 이와는 반대로 state를 복제하지 않고, 속성만 수정한다면 기존의 state 객체와 주소 값이 같기 때문에 변경감지가 되지 않는다.

그렇다면 왜 리덕스는 복제본을 만들어서 사용하도록 설계 되었는가? (주소값으로 비교하도록 설계되었는가,,?)

라는 의문이 들 것이다.

그 이유는 '성능'과 관련이 있다.

객체의 속성으로 비교하는 것은 '깊은 비교(deep-compare)'이다. 깊은 복사와 얕은 복사를 예전에 배웠는데 이때 그 지식을 바탕으로 글을 읽으니깐 더 이해가 잘 된다. 그래서 깊은 복사 & 얕은 복사에 대해 먼저 찾아보고 알고리즘을 이해한다면 훨씬 더 이해가 잘 될 것이다.

무튼, 리덕스는 state 객체의 모든 속성에 대해 변화를 감지하는데 이는 복잡한 알고리즘이 필요하다.

그리고 복잡한 알고리즘에서 주소를 통해 비교 하는 케이스가 속성을 비교한 케이스보다 약 두 배 더 빠르다는 결과를 얻었고, (state 속성이 트리구조이기때문에 이보다 더 오래 걸릴 수도 있다) 이는 성능면에서 더 좋기 때문에 이 알고리즘을 택한 듯하다.

그러므로 리듀서는 이전상태의 액션을 받아 다음상태를 반환하는 순수함수이어야만 한다. 이전 상태값을 변경하는 것이 아니라 새로운 상태값을 반환해 작성해야한다.


무튼, 이렇게 리덕스에 대해서 알아보았으니
이제 리덕스를 이용해 실습해보자.

일단, 리덕스를 순수 바닐라 자바스크립트에서 한 번 사용해보는 실습을 가져보자.

📍 VanilaJS에서 리덕스 사용해보기

일단, 디렉토리를 만들어주야겠지요~?

npm create-react-app react_vainilaJS

요로코롬 디렉토리를 하나 만들어주었다.

그리고 리덕스를 설치해준다.

npm install redux

이렇게 명령을 내려주면 package.json에 리덕스가 설치된 것을 확인할 수 있다:)

자, 시작해보자.

디렉토리 안에서 리액트 없이 바닐라자바스크립트에서 리덕스를 사용해 볼 것이기 때문에 public/index.html 파일을 아래와 같이 수정해준다.

// public / index.html 파일
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title></title>
  </head>
  <body>
    <div class="toggle"></div>
    <hr />
    <h1>0</h1>
    <button id="increase">+1</button>
    <button id="decrease">-1</button>
  </body>
</html>

효과를 주기 위한 index.css 파일은 다음과 같다.

.toggle {
  border: 2px solid black;
  width: 64px;
  height: 64px;
  border-radius: 32px;
  box-sizing: border-box;
}

.toggle.active {
  background: yellow;
}

그리고나서 index.js파일을 아래처럼 수정해준다.
각각의 단계는 주석으로 설명을 달아놓았다.

// src / index.js 파일
<script>

import "./index.css";
import { createStore } from "redux";
// 스토어를 만들기 위해서 createStore API를 사용해야하기 때문에 import 해주었다.

const divToggle = document.querySelector(".toggle");
const counter = document.querySelector("h1");
const btnIncrease = document.querySelector("#increase");
const btnDecrease = document.querySelector("#decrease");
// 1. DOM 레퍼런스 만들기
// 바닐라 자바스크립트 위에서 DOM을 직접 수정해주어야 하기 때문에 DOM 노드 값을 변수로 선언해준다.

const TOGGLE_SWITCH = "TOGGLE_SWITCH";
const INCREASE = "INCREASE";
const DECREASE = "DECREASE";
// 2. 액션 타입 정의
// 액션의 이름을 문자열 형태로 정의해준다. 주로 대문자로 작성하는데, 이유는 액션의 이름이 중복되지 않게 하기 위해서다. (고유해야함) 

const toggleSwitch = () => ({ type: TOGGLE_SWITCH });
const increase = (difference) => ({ type: INCREASE, difference });
const decrease = () => ({ type: DECREASE });
// 3. 액션 생성 함수 정의
// 액션 생성 함수를 만들어 액션 객체를 만들어 주는 함수를 작성한다. 액션 객체를 일일이 다 쓰기 번거롭기 때문이다. 함수만 넣어서 쓰면 편리하잖아유??

const initialState = {
  toggle: false,
  counter: 0,
};
// 4. 초기값 설정
// 리듀서에 들어갈 초기값을 설정한다. 초기값의 형태는 자유롭게 작성한다. 숫자, 문자열, 객체 뭐든 될 수 있겠지?

function reducer(state = initialState, action) {
  switch (action.type) {
    case TOGGLE_SWITCH:
      return {
        ...state,
        toggle: !state.toggle,
      };
    case INCREASE:
      return {
        ...state,
        counter: state.counter + action.difference,
      };
    case DECREASE:
      return {
        ...state,
        counter: state.counter - 1,
      };
    default:
      return state;
  }
}
// 5. 리듀서 함수 정의
// 리듀서 함수를 정의한다. 파라미터로 state와 action값을 받아온다.
// 리듀서 함수가 처음 호출 될때는 state값이 undefined이다. state가 undefined일때는 initalState를 기본값으로 사용하고, swtich문으로 action.type값에 따라 작은 작업을 처리할 수 있도록 한다.
// spread 연산자 또는 Object.asgin() 등을 사용해 객체의 불변성을 유지할 수 있도록 해야 한다. (순수함수)

const store = createStore(reducer);
// 6. 스토어 만들기
// 스토어를 만들기 위해 createStore API를 사용한다. 그리고 파라미터 안에 리듀서함수를 넣어주어야 한다.

const render = () => {
  const state = store.getState();

  if (state.toggle) {
    divToggle.classList.add("active");
  } else {
    divToggle.classList.remove("active");
  }

  counter.innerText = state.counter;
};
// 7. render 함수 만들기
// 상태가 업데이트 될 때마다 호출할 함수를 만들어준다.

store.subscribe(render);
// 8. 구독하기
// 스토어의 상태가 바뀔때마다 render함수가 호출되도록 만들어주기 위해 subscribe API를 사용한다.
// 파라미터로 함수를 전달하고, 전달된 함수는 액션이 발생해 상태가 업데이트 될때마다 호출된다.
// 여기서는 subscribe 함수를 직접 사용하지만 리액트에서는 이 함수를 직접 사용하지는 않는다.
// 컴포넌트에서 리덕스 상태를 조회하는 단계에서 ract-redux라는 라이브러가 이 작업을 대신함.

divToggle.addEventListener("click", function () {
  store.dispatch(toggleSwitch());
});

btnIncrease.addEventListener("click", function () {
  store.dispatch(increase(1));
});

btnDecrease.addEventListener("click", function () {
  store.dispatch(decrease());
});
// 9.  액션 디스패치시키기
// 액션을 발생시키는 것을 디스패치라고 한다. dispatch API를 통해 리듀서에게 액션객체를 전달한다.
// 파라미터는 액션객체이겠지?

</script>

💡 API 잠깐 정리하기

  • store.getState() : 현재 store에 있는 상태를 출력
  • store.dispatch(액션) : store에 등록한 reducer에 액션 객체 전달
  • store.subscribe(이벤트) : store에 전달될때마다 이벤트 호출됨
  • replaceReducer(다음 리듀서) : store에서 사용하는 reduce를 바꾼다.

이렇게 리덕스에 대해 전반적으로 알아보았고, 바닐라자바스크립트에서도 실습해 보았으므로

다음으로는,

저번 시간에 생코님의 책을 통해 리액트 컴포넌트 만들기 실습한 것을 바탕으로 리액트에서 리덕스를 사용해보자.

📍 생코님책 실습 _ React에서 Redux 사용해보기

앞에서 설명했듯,
리덕스 없이 리액트만으로 어플리케이션을 구현하면 각 컴포넌트는 서로 props로 연결되고, 다른 컴포넌트의 데이터 변경에 따른 렌더링이 필요하면 상위 컴포넌트로 이벤트가 올라가서 다시 자식 컴포넌트의 props를 통해 전달해야한다.

왼쪽의 그림에서 보듯 아주 불편하고 까다롭다.

하지만 리덕스를 이용하면 store을 통해 state을 관리할 수 있어서 편리하다.

이런 점들을 염두해두고 실습을 시작해보자.

먼저, 리덕스 스토어를 만들어야한다.
src / components 디렉토리에 store.js 파일을 생성한다.

// store.js 파일

import { createStore } from 'redux';
export default createStore(function(state, action){
	if(state === undefined) {
    	return {number:0}
    }
    if(action.type === 'INCREMENT') {
    	return {...state, number:state.number + action.size}
    }
    return state;
}, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())

createStore 라는 함수를 redux로 임포트한다.
createStore() 함수는 첫번째 인자로 reducer 함수를 받는다. 그리고 이 reducer 함수의 첫번째 인자는 state 이고, 두번째 인자는 action이다. reducer 함수는 기본적으로 state 값을 반환한다. createStore의 두번째 인자값은 크롬 확장 프로그램인 Redux DevTool을 사용하기 위해 지정한 값이다.

state가 undefined일 때 {number:0}으로 state를 초기화해주었고, 인자로 전달된 action.type값이 'INCREMENT'일때 기존 state.number 값에 인자로 전달된 action.size 값을 추가해 반환한다. 이때 speread 연산자를 사용해 기존 state는 유지하고 number 값만 변경시킨다.

그리고 createStore의 반환값을 다른 곳에 import 할 수 있게 export default 구문을 추가했다.

이제 AddNumber 컴포넌트에서 [+]버튼을 클릭했을때 데이터가 props를 통해 상위 컴포넌트로 타고 올라가는 것이 아니라, 스토어에 저장된 state값을 변경하도록 구현해보자.

// AddNumber.js 파일

import React, { Component } from 'react';
import store from '../store';

export default class AddNumber extends Component {
	state = {size:1}
    render(){
    	return(
        	<div>
            	<h1>Add Number</h1>
                <input type="button" value="+" onClick={function(){
                	store.dispatch({type:'INCREMENT', size: this.state.size})'
                }.bind(this)}></input>
                <input type="text" value={this.state.size} onChange={function(e){
                	this.setState({size:Number(e.target.value)});
                }.bind(this)}></input>
            </div>
        );
    }
}

store을 import하였다. 그리고 store의 내장함수인 dispatch라는 메서드를 이용해 값과 type을 전달했다. type값은 'INCREMENT'이고, size값으로는 AddNumber 컴포넌트의 state.size 값을 전달하였다.

잘 구현됐는지 확인하기 위해 리덕스의 state를 확인해보자. 크롬 확장 프로그램인 Redux DevTool을 이용해보자.

type에 'INCREMENT'가, size에 입력한 값이 리덕스 스토어에 잘 전달된 것을 확인할 수 있다.

이제 리덕스의 스토어에 저장된 size값이 DisplayNumber 컴포넌트의 텍스트 입력상자에 전달되게 해보자.

// DisplayNumber.js 파일

import React, { Component } from 'react';
import store from '../components/store';

export default class DisplayNumber extends Component {
	state = {number:store.getState().number}
    constructor(props) {
    	super(props);
        store.subscribe(function(){
        	this.setState({number:store.getState().number});
        }.bind(this));
    }
    render() {
    	return(
        	<div>
            	<h1>Display Number</h1>
                <input type="text" value={this.state.number}></input>
            </div>
        );
    }
}

우선 AddNumber 컴포넌트에서 했던 것처럼 store를 import 한다.

그리고 DisplayNumber의 텍스트 입력상자의 value값이 더이상 props를 통해 전달되는 값이 아니기 때문에 store에 있는 데이터를 사용해야한다. 그렇게 하기 위해서는 DisplayNumber에 state를 추가한다. store.getState() 함수로 리덕스 스토어의 state를 가져와 DisplayNumber의 state에 있는 number 값에 전달한다.

그리고 컴포넌트가 스토어의 state 값이 변경됐다는 사실을 통보받을 수 있게 구독한다. 이 부분은 constructor 안에 작성했다.

constructor의 super(props); 부분은 constructor의 맨 윗줄에서 반드시 실행하도록 약속된 코드이다.

store의 subscribe 메서드를 통해 구독 기능을 구현했는데 subscribe의 첫번째인자는 함수이고 이 함수는 리덕스 스토어의 값이 변경됐을때 호출된다. 따라서 이 함수에서 리덕스 스토어의 state를 가져와 DisplayNumber의 state에 넣는다.

이렇게 하면 직접 리덕스 스토어와 연결되어 있기 때문에 컴포넌트의 깊이가 깊어지더라도 리덕스 스토어의 값이 변경되면 state가 변경되고 그에 따라 스토어를 구독하는 모든 컴포넌트의 state가 변경되면서 각 컴포넌트의 render함수가 호출된다.

이렇게 되면 데이터를 주고 받기 위해 사용했던 props는 필요 없어진다.

// App.js 파일
... 생략 ...

class App extends Component {
	state = {number: 10}
    render() {
    	return(
        	<div className="App">
            	<h1>Root</h1>
                <AddNumberRoot></AddNumberRoot>
                <DisplayNumberRoot></DisplayNumberRoot>
            </div>
        );
    }
}
... 생략 ...
// AddNumberRoot.js 파일

import React, { Component } from 'react';
import AddNumber from '../components/AddNumber';

export default class AddNumberRoot extends Component {
	render(){
    	return(
        	<div>
            	<h1>Add Number Root</h1>
                <AddNumber></AddNumber>
            </div>
        );
    }
}
// DisplayNumberRoot.js 파일

import React, { Component } from 'react';
import DisplayNumber from '../components/DisplayNumber';

export default class DisplayNumberRoot extends Component {
	render(){
    	return(
        	<div>
            	<h1>Display Number Root</h1>
                <DisplayNumber></DisplayNumbe>
            </div>
        );
    }
}

요로코롬 리액트에 리덕스를 넣음으로써 훨씬 더 간편하게 어플리케이션의 상태를 관리할 수 있게 되었다. 그리고 Redux DevTool이라는 크롬 확장 기능을 통해 상태의 변화까지 검사할 수 있는 것을 리덕스를 통해 확인할 수 있게 되었다.


출처
https://redux.js.org/tutorials/essentials/part-1-overview-concepts
https://jeong-pro.tistory.com/23
https://boxfoxs.tistory.com/406
https://dreamcoding.tistory.com/100#article-1--%EB%A6%AC%EB%8D%95%EC%8A%A4%EB%9E%80?
https://choi-hyunho.tistory.com/198
생활코딩! 리액트 프로그래밍 책

profile
프론트엔드개발자가 되고 싶어서 열심히 땅굴 파는 자

0개의 댓글