상세 컨텐츠

본문 제목

[리액트 스타터1] 16장. 리덕스 라이브러리 이해하기

22-23/22-23 리액트 스타터 1

by 2jo 2023. 1. 12. 10:00

본문

728x90

 

리덕스가장 많이 사용하는 리액트 상태 관리 라이브러리입니다. 리덕스를 사용하면 컴포넌트의 상태 업데이트 관련 로직을 다른 파일로 분리시켜서 더욱 효율적으로 관리할 수 있습니다. 또한, 컴포넌트끼리 같은 상태를 공유해야 할 때도 여러 컴포넌트를 거치지 않고 손쉽게 상태 값을 전달하거나 업데이트할 수 있습니다. 리덕스 라이브러리는 전역 상태를 관리할 때 효과적입니다. 코드의 유지 보수성을 높이고, 작업 효율을 극대화할 수 있습니다. 리덕스는 편리한 개발자 도구를 지원하고 미들웨어 기능을 제공하여 비동기 작업을 훨씬 효율적으로 관리할 수 있습니다.

이전 장에서 배운 Context API로 같은 작업을 할 수 있습니다. 단순히 전역 상태 관리만 요구된다면 Context API를 사용하는 것으로 충분합니다. 그러나 리덕스를 사용하면 상태를 체계적으로 관리할 수 있기 때문에 프로젝트 규모가 크다면 리덕스를 사용하는 것이 좋습니다.

이번 장에서는 핵심 키워드를 알아보고, Parcel로 프로젝트를 구성하고, 토글 스위치와 카운터를 구현해 보도록 하겠습니다.


16.1 개념 미리 정리하기

앞으로 리덕스를 사용하면서 접하게 될 키워드의 개념을 간략히 알아보겠습니다.

 

 

 

16.1.1 액션

상태에 어떠한 변화가 필요하면 액션이 발생합니다. 이는 하나의 객체로 표현되는데, 액션 객체는 다음과 같은 형식으로 이루어져 있습니다.

{
  type: ‘TOGGLE_VALUE’
  data: {
    id: 1,
    text: ‘리덕스 배우기’
  }
}

액션 객체는 type 필드를 반드시 가지고 있어야 합니다. 이 값이 액션의 이름입니다. 그리고 그 외 값들은 나중에 상태 업데이트를 할 때 참고해야 할 값이고, 작성자 마음대로 넣을 수 있습니다.

 

 

 

16.1.2 액션 생성 함수

액션 생성 함수액션 객체를 만드는 함수입니다. 화살표 함수로도 작성할 수 있습니다.

function addTodo(data) {
  return {
    type: 'ADD_TODO',
    data
  };
}
const changeInput = text => ({
	type: 'CHANGE_INPUT',
	text
});

 

어떤 변화를 일으켜야 할 때마다 액션 객체를 만들어야 하는데 매번 액션 객체를 직접 작성하기 번거로울 수 있고, 만드는 과정에서 정보를 놓칠 수도 있습니다. 이러한 일을 방지하기 위해 함수로 만들어서 관리합니다.

 

 

 

16.1.3 리듀서

리듀서변화를 일으키는 함수입니다. 액션을 만들어서 발생시키면 리듀서가 현재 상태와 전달받은 액션 객체를 파라미터로 받아옵니다. 그리고 두 값을 참고하여 새로운 상태를 만들어서 반환합니다. 리듀서 코드는 다음의 형태로 이루어져 있습니다.

const initialState = {
  counter: 1
};

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

 

 

 

16.1.4 스토어

프로젝트에 리덕스를 적용하기 위해 스토어를 만듭니다. 한 개의 프로젝트는 단 하나의 스토어만 가질 수 있습니다. 스토어 안에는 현재 애플리케이션 상태와 리듀서가 들어가 있으며, 그 외에도 몇 가지 중요한 내장 함수를 지닙니다.

 

 

 

16.1.5 디스패치

디스패치스토어 내장 함수 중 하나입니다. 디스패치는 액션을 발생시키는 것이라고 이해하면 됩니다. 이 함수는 dispatch(action)과 같은 형태로 액션 객체를 파라미터로 넣어서 호출합니다. 이 함수가 호출되면 스토어는 리듀서 함수를 실행시켜서 새로운 상태를 만들어 줍니다.

 

 

 

16.1.6 구독

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

const listener = () => {
	console.log('상태가 업데이트됨');
}
const unsubscribe = store.subscribe(listener);

unsubscribe();

추후 구독을 비활성화할 때 unsubscribe 함수를 호출합니다.

 

 


16.2 리액트 없이 쓰는 리덕스

리덕스는 리액트에 종속적인 라이브러리가 아닙니다. 리액트에서 사용하려고 만들어졌지만 다른 UI 라이브러리 혹은 프레임워크와 함께 사용될 수 있습니다. ngular-redux, ember redux, Vue에서 사용할 수 있습니다.

, 리덕스는 바닐라 자바스크립트(라이브러리, 프레임워크 없이 사용하는 순수 자바스크립트)와 함께 사용할 수 있습니다. 이번 소단원에서는 바닐라 자바스크립트 환경에서 리덕스를 사용하며 핵심 기능과 작동 원리를 이해해 봅시다.

 

 

 

16.2.1 Parcel로 프로젝트 만들기

프로젝트를 구성하기 위해 Parcel 도구를 사용하겠습니다. 이 도구를 사용하면 쉽고 빠르게 웹 애플리케이션 프로젝트를 구성할 수 있습니다. 먼저 parcel-bundler를 설치합시다.

$ yarn global add parcel-bundler
#yarn global이 잘 설치되지 않는다면 npm install -g parcel-bundler 입력

 

프로젝트 디렉터리를 생성한 후 package.json 파일을 생성하세요.

$ mkdir vanilla-redux
$ cd vanilla-redux
$ yarn init -y #package.json 파일 생성

 

에디터로 해당 디렉터리를 열어서 index.html index.js 파일을 만들어 주세요.

<!--index.html-->
<html>
  <body>
    <div>바닐라 자바스크립트</div>
    <script src="./index.js"</script> <!--콘솔 오류 시 "" 안의 .을 제거하여 해결-->
  </body>
</html>
//index.js
console.log('hello parcel');

 

다 작성한 후 다음 명령어를 실행하면 개발용 서버가 실행됩니다.

$ parcel index.html
Server running at http://localhost:1234
Built in 548ms.

개발 서버의 주소는 http://localhost:1234/이며, 파일을 저장할 때마다 자동으로 새로고침됩니다. 브라우저로 해당 주소에 들어가면 다음과 같은 페이지가 실행됩니다.

실행된 http://localhost:1234/

 

yarn을 사용하여 리덕스 모듈을 설치하세요.

$ yarn add redux

 

 

 

16.2.2 간단한 UI 구성하기

간단한 스타일 파일을 작성합니다.

/*index.css*/

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

.toggle.active {
	background: yellow;
}

 

index.html을 수정하세요.

<!--index.html-->

<html>
	<head>
		<link rel="stylesheet" type="text/css" href="index.css" />
	</head>
	<body>
  		<div class="toggle"></div>
  		<hr />
		<h1>0</h1>
		<button id="increase">+1</button>
		<button id="decrease">-1</button>
		<script src="./index.js"></script>
	</body>
</html>

 

 

다음과 같이 간단한 UI를 구성했습니다.

실행된 http://localhost:1234/

 

 

 

 

 

16.2.3 DOM 레퍼런스 만들기

이번 프로젝트에서는 UI를 관리할 때 별도의 라이브러리를 사용하지 않기 때문에 DOM을 직접 수정해야 합니다. 자바스크립트 파일 상단에 수정할 DOM 노드를 가리키는 값을 미리 선언합니다. 기존 코드는 지웁니다.

//index.js
const divToggle = document.querySelector('.toggle');
const counter = document.querySelector('h1');
const btnIncrease = document.querySelector('#increase');
const btnDecrease = document.querySelector('#decrease');

 

 

 

16.2.3 액션 타입과 액션 생성 함수 정의

프로젝트의 상태에 변화를 일으키는 것을 액션이라고 합니다. 먼저 액션에 이름을 정의하겠습니다. 액션 이름은 문자열 형태, 주로 대문자로 작성하며 액션의 이름은 고유해야 합니다. 이름은 중복되면 프로그래머가 의도하지 않은 결과가 발생할 수도 있습니다.

//index.js 파일에 이어서 작성
const TOGGLE_SWITCH = 'TOGGLE_SWITCH';
const INCREASE = 'INCREASE';
const DECREASE = 'DECREASE';

 

다음으로 이 액션 이름을 사용하여 액션 객체를 만드는 액션 생성 함수를 작성합니다. 액션 객체는 type 을 반드시 가져야 하며, 그 외에 추후 상태를 업데이트할 때 참고하고 싶은 값은 마음대로 넣을 수 있습니다.

//index.js 파일에 이어서 작성
const toggleSwitch = () => ({ type: TOGGLE_SWITCH });
const increase = difference => ({ type: INCREASE, difference });
const decrease = () => ({ type: DECREASE });

 

 

 

16.2.3 초깃값 설정

이 프로젝트에서 사용할 초깃값을 정의합시다. 초깃값의 형태는 자유입니다. 숫자, 문자열, 객체 상관없습니다.

//index.js 파일에 이어서 작성
const initialState = {
	toggle: false,
	counter: 0
};

 

 

 

16.2.3 리듀서 함수 정의

리듀서는 변화를 일으키는 함수입니다. 함수의 파라미터로 stateaction 을 받습니다.

//index.js 파일에 이어서 작성
function reducer(state = initialState, action) {
	switch (action.type) { //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;
	}
}

리듀서 함수가 처음 호출될 때는 state 값이 undefined입니다. 해당 값이 undefined로 주어졌을 때 initialState를 기본값으로 설정하기 위해 함수의 파라미터에서 기본값을 설정하였습니다. 리듀서는 상태의 불변성을 유지하면서 데이터에 변화를 일으켜야 합니다. 이 작업을 할 때 spread 연산자(...)를 사용하면 편합니다. , 객체의 구조가 복잡해지면 spread 연산자를 사용하는 것이 번거롭고 가독성 또한 나빠지기 때문에 객체의 구조가 복잡해지거나 배열을 다루는 경우 immer 라이브러리를 사용하면 쉽게 리듀서를 작성할 수 있습니다.

 

 

 

16.2.7 스토어 만들기

스토어를 만들 때는 createStore 함수를 사용합니다. 이 함수를 사용하려면 코드 상단에 import 구문을 사용하여 리덕스에서 createStore 함수를 불러와야 하고, 함수의 파라미터에 리듀서 함수를 넣어야 합니다.

//index.js 파일의 상단
import { createStore } from 'redux';
//index.js 파일에 이어서 작성
const store = createStore(reducer);

 

 

 

16.2.8 render 함수 만들기

render 함수는 상태가 업데이트될 때마다 호출되며, 리액트의 render 함수와 다르게 이미 html을 사용하여 만들어진 UI의 속성을 상태에 따라 변경합니다.

//index.js 파일에 이어서 작성
const render = () => {
	const state = store.getState(); // 현재 상태 불러오기
	if (state.toggle) {// 토글 처리
		divToggle.classList.add('active');
	} else {
		divToggle.classList.remove('active');
	}
	counter.innerText = state.counter; // 카운터 처리
};

 

 

 

16.2.9 구독하기

스토어의 상태가 바뀔 때마다 render 함수가 호출되도록 해봅시다. 이 작업은 스토어의 내장 함수 subscribe를 사용하여 수행합니다. subscribe 함수의 파라미터로 함수 형태의 값을 전달합니다. 전달된 함수는 추후 액션이 발생하여 상태가 업데이트될 때마다 호출됩니다.

이번 프로젝트에서는 subscribe 함수를 직접 사용하지만, 추후 리액트 프로젝트에서 리덕스를 사용할 때는 이 함수를 직접 사용하지 않고 react-redux라는 라이브러리로 처리할 것입니다.

//index.js 파일에 이어서 작성
store.subscribe(render);

 

 

 

16.2.10 액션 발생시키기

액션을 발생시키는 것디스패치라고 부릅니다. 디스패치할 때는 스토어의 내장 함수 dispatch를 사용합니다. 파라미터로 액션 객체를 넣으면 됩니다. DOM 요소에 클릭 이벤트를 설정합니다. 이벤트 함수 내부에서 dispatch 함수를 사용하여 액션을 스토어에게 전달합니다.

//index.js 파일에 이어서 작성
divToggle.onclick = () => {
	store.dispatch(toggleSwitch());
};
btnIncrease.onclick = () => {
	store.dispatch(increase(1));
};
btnDecrease.onclick = () => {
	store.dispatch(decrease());
};

 

화면에 나타나는 원과 하단의 버튼을 클릭하면 상태 변화가 잘 일어나는 것을 확인할 수 있습니다.

실행된 http://localhost:1234/

 

 


11.3 리덕스의 세 가지 규칙

리덕스를 프로젝트에서 사용할 때 지켜야 하는 세 가지 규칙이 있습니다.

 

 

 

16.3.1 단일 스토어

하나의 애플리케이션 안에는 하나의 스토어가 있습니다. 여러 개의 스토어를 사용할 수도 있지만, 상태 관리가 복잡해질 수 있기 때문에 권장하지 않습니다.

 

 

 

16.3.2 읽기 전용 상태

리덕스 상태는 읽기 전용입니다. 기존 리액트에서 setState를 사용하여 state를 업데이트할 때 불변성을 지키기 위해 spread 연산자를 사용하거나 immer 같은 불변성 관리 라이브러리를 사용하였는데, 리덕스도 마찬가지입니다. 상태를 업데이트할 때 기존의 객체는 건드리지 않고 새로운 객체를 생성해야 합니다.

리덕스에서 불변성을 유지해야 하는 이유는 데이터 변경을 감지하기 위해 얕은 비교 검사를 하기 때문입니다. 그래야 좋은 성능을 유지할 수 있기 때문입니다.

 

 

 

16.3.3 리듀서는 순수한 함수

변화를 일으키는 리듀서 함수는 순수한 함수여야 합니다. 순수한 함수란 다음 조건을 만족합니다.

  • 함수는 이전 상태와 액션 객체를 파라미터로 받는다.
  • 파라미터 외의 값은 의존하지 않는다.
  • 이전 상태는 건드리지 않고, 변화 준 새로운 상태 객체를 만들어서 반환한다.
  • 같은 파라미터로 호출된 함수는 언제나 같은 결과 값을 반환한다.

리듀서를 작성할 땐 위의 네 가지 사항을 주의해야 합니다. 함수 내부에서 랜덤 값을 만들거나, 외부 함수에서 값을 가져오거나, 네트워크 요청 등을 한다면 파라미터가 같아도 다른 결과를 만들어 낼 수 있기 때문에 순수한 함수가 아니게 됩니다. 이러한 작업은 리듀서 함수 밖(액션 제작, 리덕스 미들웨어 등)에서 처리해야 합니다.

 

 


16.4 정리

지금까지 리덕스 라이브러리가 어떤 방식으로 작동하는지 알아보았습니다. 다음 장에서는 리액트 프로젝트에서 리덕스를 사용하는 방법을 알아보는데, 리덕스 코드를 작성하는 흐름은 이번 장에서 했던 것과 유사합니다. 액션 생성 함수를 작성하고, 리듀서를 작성하고, 스토어를 만듭니다. 이번 프로젝트에서는 함수에서 스토어를 구독하는 작업을 직접 하였으나,다음 장에서는 react-redux라는 라이브러리를 사용하여 스토어의 상태가 업데이트될 때마다 컴포넌트를 리렌더링하겠습니다.


Quiz

1. 가장 많이 사용하는 리액트 상태 관리 라이브러리는 (________)이다.

리덕스

2. 액션은 상태 (________)가 필요할 때 발생하고, (________)를 반드시 가지고 있어야 합니다.

변화, type 필드

3. 액션 객체를 만드는 함수를 (________)라고 부른다.

액션 생성 함수

4. 변화를 일으키는 함수를 (________)라고 부른다.

리듀서

5. 프로젝트에 리덕스를 적용하기 위해 (________)를 만들고, 한 개의 프로젝트는 단 하나의 (________)만 가진다.

스토어, 스토어

6. (________)(________)은 스토어 내장 함수 중 하나이다.

디스패치, 구독

7. 순수한 함수의 조건이다. 빈칸을 채워라.

함수는 (________)(________)를 파라미터로 받는다.

(________) 외의 값은 의존하지 않는다.

(________)는 건드리지 않고, 변화 준 새로운 (________)를 만들어서 반환한다.

같은 (________)로 호출된 함수는 언제나 같은 (________)을 반환한다.

함수는 이전 상태와 액션 객체를 파라미터로 받는다.

파라미터 외의 값은 의존하지 않는다.

이전 상태는 건드리지 않고, 변화 준 새로운 상태 객체를 만들어서 반환한다.

같은 파라미터로 호출된 함수는 언제나 같은 결과 값을 반환한다.

8. 이번 장에서 다룬 액션 생성 함수 작성 코드이다. 빈 칸을 채워라.

const toggleSwitch = () => ({ ________: TOGGLE_SWITCH });
const increase = difference => ({ ________: INCREASE, difference });
const decrease = () => ({ ________: DECREASE });
//정답
const toggleSwitch = () => ({ type: TOGGLE_SWITCH });
const increase = difference => ({ type: INCREASE, difference });
const decrease = () => ({ type: DECREASE });

 

9. 스토어를 만들 때 파일 상단과 하단에 작성하는 코드를 작성하라.

//정답
import { createStore } from 'redux';
const store = createStore(reducer);

Corner React1

Editor: 동동

728x90

관련글 더보기