본문 바로가기
React

2021.08.10 redux library-tutorial

by 해맑은 코린이 2021. 8. 10.

2021.08.10 redux library-tutorial_정리노트

 

오늘은 드디어 리덕스를 들어간다...!

 

 

 

리덕스는 리액트뿐만 아니라, Js App 에서 예측 가능한 상태들을 관리하는 컨테이너 라이브러리!

그러니 리액트에서 사용하려고 만들어진 라이브러리지만, 다른 라이브러리/ 프레임워크와 함께 사용할 수 있다는 말!

 

 

리덕스는 바닐라 자바스크립트와 함께 사용할 수도 있음. ( 라이브러리, 프레임워크 없이 사용하는 순수 자바스크립트 그 자체. )

 

 

 

오늘은 그래서 리덕스를 어떻게 쓰는지. 각 키워드가 무슨 역할을 하는지 등등에 대해 튜토리얼처럼 포스팅할 예정.

 

 

키워드의 개념부터 정리하고 흐름에 따라서 코드 작성을 해봅시다~!!!!!!!!

 

 

action

상태에 어떠한 변화가 필요하면 action 이란 것이 발생. 이는 하나의 객체로 표현되며, 예시 코드를 보면

 

{

	type : 'TOGGLE_VALUE'

}

 

이렇게 객체형식으로 나타나는데, 액션 객체는 반드시 type 필드를 가지고 있어야 한다. 이 값을 액션의 이름이라고 생각하면 된다. 

그리고 그 외의 값들은 나중에 상태 업데이트를 할 때 참고해야할 값이며, 작성자 마음대로 넣을 수 있다. 

 

{
	type:'ADD_TODO',
    data:{
    	id:1,
        text: '리덕스 배우기'
    }
}


{
	type:'CHANGE_INPUT',
    data:{
    
        text: '안녕'
    }
}

 

이렇게! type 과 함께 넣고 싶은 것들을 작성할 수 있다.

 

 

액션 생성 함수

액션 객체를 만들어 주는 함수

 

function addTodo(data){
	return{
    	type:'ADD_TODO',
        data
    };
}



//arrow function 형태


const changeInput = text =>({
    type:'CHANGE_INPUT',
    text
    
});

 

어떤 변화를 일으켜야 할 때마다 액션 객체를 만들어야 하는데 매번 액션 객체를 직접 작성하기 번거로울 수도 있고, 실수가 일어날 수도 있기 때문에 이를 함수로 만들어서 관리한다.

 

 

리듀서

reducer 는 변화를 일으키는 함수. 액션을 만들어서 발생시키면 리듀서가 현재 상태와 전달받은 액션 객체를 파라미터로 받아온다.

그리고 두 값을 참고하여 새로운 상태를 만들어서 반환해줌!

 

const initialState = {
	counter : 1
};

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

}

 

 

스토어 

프로젝트에 리덕스를 적용하기 위해 스토어를 만든다. 한 개의 프로젝트는 단 하나의 스토어만 가질 수 있다.

스토어 안에는 현재 애플리케이션 상태와 리듀서가 들어가 있으며, 그 외에도 몇 가지 중요한 내장함수가 있다. 

 

 

디스패치

디스패치는 스토어의 내장 함수 중 하나이며, '액션을 발생 시키는 것' 이라고 이해하자!

이 함수는 dispath(action) 과 같은 형태로 액션 객체를 파라미터로 넣어서 호출함. 이 함수가 호출되면, 스토어는 리듀서 함수를 실행시켜서 새로운 상태를 만들어줌.

 

 

구독

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

 

const listener = () =>{
	console.log('상태가 업데이트 되었습니다.')

}

const unsubscribe = store.subscribe(listener);



 //추후 구독을 비활성화 할 때 함수를 호출


unsubscribe();

 

 

 

 


 

 

책에서는 리덕스의 작동원리와 핵심 기능등을 이해하기 위해서 바닐라 자바스크립트를 사용하고 Parcel 이라는 도구를 사용해서 정리하고 있군. 그럼 나도 따라 가야지 ㅎㅅㅎ 설치 ㄱ ㄱ

 

 

$ yarn global add parcel-bundler

 

혹시나 yarn 이 안된다면 npm install -g parcel-bundler로 ㄱ ㄱ

 

 

// 새 디렉토리 생성
$ mkdir vanilla-redux

$ cd vanilla-redux

// package.json 파일 생성
$ yarn init -y

 

 

그러고 index.html, index.js 파일 생성!

 

 

 

 

 

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>바닐라 자바스크립트</div>
    <script src="index.js"></script>
</body>
</html>

 

 

index.js

console.log('hello parcel')

 

 

요롷게 간단히 적고 parcel 로 서버 실행을 시켜보자!

 

$ parcel index.html

 

 

http://localhost:1234/ 이고, 파일을 저장할 때마다 자동으로 새로고침!

 

 

 

요롷게 잘 뜬다면 성공쓰

 

 

 

redux를 본격적으로 설치하고 써봅시다!

 

 

$ yarn add redux

 

 

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Redux-tutorial</title>
    <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>

 

index.html 을 이렇게 수정해서 간단하게 css 도 

 

 

 

index.css

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

.toggle .active{
    background: yellow;
}

 

 

 

 

먼가.....간만에 html 쓰니까 되게 어색..하네 ..^ㅎ^ 쨋든 간단하게 UI 만듬!

 

 

바닐라 자바스크립트에선 별도의 라이브러리를 사용하지 않기 때문에 DOM 을 직접 수정해주어야 한다. 

 

 

index.js

 

 

 

요롷게!! 

 

 

후 기본 세팅은 끝났으니 액션부터 차근 차근 ㄲ

 

프로젝트의 상태에 변화를 일으키는 것이 아까 액션이라고 했음. 액션 이름은 문자열 형태로, 주로 대문자로 작성하며 액션의 이름은 고유해야 한다. 이름이 중복되면 의도하지 않은 결과가 발생할 수 있기 때문!

 

 

index.js

const divToggle = document.querySelector(".toggle");
const counter = document.querySelector("h1");
const btnIncrease = document.querySelector("#increase");
const btnDecrease = document.querySelector("#decrease");
// createStore import
import { createStore } from "redux";

// 액션 이름
const TOGGLE_SWITCH = "TOGGLE_SWITCH";
const INCREASE = "INCREASE";
const DECREASE = "DECREASE";

// 액션 객체를 만드는 액션 생성 함수를 작성. 반드시 type 값을 가지고 있어야 하며, 그 외에 추후 상태를 업데이트 할 때 참고하고 싶은 값은 마음대로 넣을 수 있음.

const toggleSwitch = () => ({
  type: TOGGLE_SWITCH,
});

const increase = (difference) => ({
  type: INCREASE,
  difference,
});

const decrease = () => ({
  type: DECREASE,
});

// 초깃값 설정 : 형태는 자유. 숫자, 문자열, 객체 등등 자유

const initialState = {
  toggle: false,
  counter: 0,
};

// 리듀서 함수 정의 : 변화를 일으키는 함수. 파라미터러는 state, action 값을 받아옴.

// state가  undefined일 때는  initialState 를 기본값으로 사용 >> 처음 호출될 때 는 undefinend
function reducer(state = initialState, action) {
  // action.type 에 따라 다른 작업을 처리함.
  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;
  }
}

// store 생성

const store = createStore(reducer);

// store 내장 함수들을 사용해서 render 함수 만들기

// 상태가 업데이트 될 때마다 호출되며, 리액트의 render 함수와는 다름. 이미 html 을 사용하여 만들어진 UI 의 속성을 상태에 따라 변경.
const render = () => {
  // 현재 상태를 불러옴.
  const state = store.getState();
  // 토글 처리
  if (state.toggle) {
    divToggle.classList.add("active");
  } else {
    divToggle.classList.remove("active");
  }
  // 카운터 처리
  counter.innerText = state.counter;
};

render();
// 스토어의 상태가 바뀔 때마다 방금 만든 render 함수가 호출되도록 스토어의 내장 함수 subscribe 사용.
store.subscribe(render);


// 액션을 발생시키는 스토어의 내장 함수 dispatch 사용. 파라미터에는 액션의 객체를 넣어줌.
divToggle.onclick = () => {
  store.dispatch(toggleSwitch());
};

btnIncrease.onclick = () => {
  store.dispatch(increase(1));
};

btnDecrease.onclick = () => {
  store.dispatch(decrease());
};

 

어우..길다 위의 키워드의 흐름에 따라 작성해보았다. 

몇 개 짚고 갈 점은, 리듀서에서는 상태의 불변성을 유지하면서 데이터에 변화를 일으켜 주어야 한다. 이 때 spread 연산자를 사용하면 편한데, 객체의 구조가 복잡해지면 이 연산자를 쓰기에 복잡해지고, 코드의 가독성이 나빠지기 때문에 리덕스의 상태는 최대한 깊지 않은 구조로 진행하는 것이 좋다. ( 객체의 구조가 복잡하거나 배열을 다루는 경우에 immer 라이브러리를 쓰는 것이 더 편하다! )

 

또, 이번 프로젝트에서는 구독함수를 직접 사용해주었지만, 추후 리액트에서 리덕스를 사용할때는 이 함수를 직접 사용하지 않을 것! 

왜냐 , react-redux 라이브러리에서는 컴포넌트에서 리덕스 상태를 조회해주기 때문~!!

 

 

실행 화면

 

 

 

흐어 여기까지 흐름인데 이건 진짜 줄줄 치면서 흐름을 한번 더 봐야할듯. 

 

 

 

마지막으로 리덕스의 세 가지 규칙 정리하고 끝내보잘

 

단일 스토어

하나의 애플리케이션 안에는 하나의 스토어가 들어있다. 

 

특정 업데이트가 너무 빈번하게 일어나거나 애플리케이션의 특정 부분을 완전히 분리시킬 때 경우 등등.... 이럴 때 여러 개의 스토어를 만드는 것이 완전히 불가능 하지는 않다. 하지만 상태 관리가 매우 복잡해지니 권장하지 않음.

 

 

읽기 전용 상태

리덕스 상태는 읽기 전용! 

 

기존에 리액트에서 state 를 업데이트를 할 때에도 객체나 배열을 업데이트를 하는 과정에서 불변성을 지켜주기 위해 spread 연산자를 사용하거나 immer 같은 불변성 라이브러리를 사용한 것처럼 리덕스도 마찬가지!

상태를 업데이트할 때에는 반드시 기존의 객체는 건드리지마 않고 새로운 객체를 생성해주자

왜? 내부적으로 데이터가 변경되는 것을 감지하기 위해 얕은 비교 검사를 하기 때문!

또 왜 얕은 비교...? 객체의 변화를 감지할 때 객체의 깊숙한 안쪽까지 비교하지 않고 겉핥기 식으로 비교하기 때문에 좋은 성능을 유지할 수 있음. 

 

 

리듀서는 순수한 함수

 

순수 함수 조건

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

파라미터 외의 값에는 의존하면 안된다.

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

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

 

리듀서를 작성할 때는 위의 네 가지 사항을 준수해야한다.

 

예로, 리듀서 함수 내부에서 랜덤 값을 만들거나, Data 함수를 사용하여 현재 시간을 가져온다거나, 네트워크 요청을 한다면 파라미터가 같아도 다른 결과를 만들어 낼 수 있기 때문에 사용하면 안됨!!!!! 

이러한 작업은 리듀서 함수 바깥에서 처리해주어야함. 

액션을 만드는 과정에서 처리한다거나, 나중에 배울 리덕스 미들웨어에서 처리해도 된다. 주로 네트워크 요청과 같은 비동기 작업은 미들웨어를 통해 관리!

 

 

'React' 카테고리의 다른 글

2021.08.18 React react-redux, redux-middleware  (0) 2021.08.18
2021.08.16 React-redux  (2) 2021.08.16
2021.08.08 React Context API  (2) 2021.08.08
2021.08.07 React 외부 API 호출  (0) 2021.08.07
2021.08.03 리액트 라우터로 SPA 개발하기  (3) 2021.08.03

댓글