본문 바로가기
Frontend/Libraries

[Redux] 리덕스 라이브러리 이해하기

by 디스코비스킷 2023. 12. 28.
반응형

리덕스는 가장 많이 사용하는 리액트 상태관리 라이브러리다. 리덕스를 사용하면 컴포넌트의 상태 업데이트 관련 로직을 다른 파일로 분리시켜서 더욱 효율적으로 관리할 수 있다.
단순 전역상태관리는 Context API를, 프로젝트의 규모가 클 경우에는 리덕스를 사용하는편이 좋다.

리덕스 장점

  • 코드의 유지보수성 높여줌
  • 작업효율 극대화
  • 편리한 개발자도구 지원
  • 미들웨어 기능 제공 -> 비동기 작업을 훨씬 효율적으로 관리

개념 미리 정리하기

액션

상태에 어떠한 변화가 필요하면 액션이 발생.
액션은 하나의 객체로 표현된다. 액션 객체는 type 필드를 반드시 가지고 있어야한다.
이값은 액션의 이름이라고 생각하면 된다. 그 외의 값들은 상ㅇ태 업데이트를 할때 참고할 값이며, 마음대로 넣을 수 있음.

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

액션 생성함수

액션 생성 함수(action creator)는 액션 객체를 만들어 주는 함수다.

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

어떤 변화를 일으켜야할때마다 액션 객체를 만들어야 하는데 매번 액션 객체를 직접 작성하기 번거로움+ 유지보수성 낮음. -> 때문에 *함수로 만들어서 관리한다. *

리듀서

리듀서(reducer)는 변화를 일으키는 함수다. 액션을 만들어서 발생시키면 리듀서가 현재 상태와 전달받은 액션 객체를 파라미터로 받아온다. 두 값을 참고하여 새로운 상태를 만들어서 반환한다.

const initialState = {
  counter: 1,
}

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

스토어

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

디스패치

디스패치(dispatch)는 스토어의 내장함수 중 하나다. 디스패치는 액션을 발생시키는것이다. dispatch(action)으로 액션객체를 파라미터로 넣어서 호출한다.

구독

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

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

const unsubscribe = store.subscribe(listener);

unsubscribe(); // 구독 비활성화 할때 함수를 호출

리액트 없이 쓰는 리덕스

리액트는 리액트에 종속되는 라이브러리가 아니다. 다른 UI라이브러리, 프레임워크와 함께 사용할 수도 있다. (앵귤러, ember, 뷰(리덕스를 사용할수도 있지만 주로 vuex를 사용))
리덕스는 바닐라자바스크립트와 함께 사용할 수도 있다.

바닐라 자바스크립트 환경에서 리덕스의 핵심기능과 작동원리를 알아보자.

Parcel로 프로젝트 만들기

$ yarn global add parcel-bundler
$ mkdir vanilla-redux
$ cd vanilla-redux
$ yarn init -y pakage.json 파일 생성

index.html과 index.js를 만들어 간단히 Hello world를 나타내보자.

index.js는 body 안의 제일 하단에 넣거나 head안에 위치시 defer라는 속성을 추가해준다.
defer 속성은 스크립트를 백그라운들에서 다운로드후 스크립트 실행은 페이지구성이 끝날때까지 지연시켜줘서 DOM이 준비된 후 스크립트를 실행하기때문에 innerText, classList등 undefined나 null이되는 문제(Cannot set property 'innerText' of null)를 방지할 수 있다.

$ parcel index.html 실행

잘되는것을 확인한후 리덕스를 설치한다.
$ yarn add redux

간단한 UI구성하기

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./index.js"></script>
    <link rel="stylesheet" href="./index.css">
</head>
<body>
    <div class="toggle"></div>
    <hr />
    <h1>0</h1>
    <button id="increase">+1</button>
    <button id="decrease">-1</button>
</body>
</html>

DOM레퍼런스 만들기

// DOM 선택
const divToggle = document.querySelector(".toggle");
const counter = document.querySelector("h1");
const btnIncrease = document.querySelector("#increase");
const btnDecrease = document.querySelector("#decrease");

UI를 관리할때 별도 라이브러리를 사용하지 않기때문에 직접 DOM을 수정해야한다.
index.js파일에 DOM노드를 가리키는 값을 미리 선언해준다.

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

// 액션 type
const TOGGLE_SWITCH = 'TOGGLE_SWITCH';
const INCREASE = 'INCREASE';
const DECREASE = 'DECREASE';

// 액션 생성 함수
const toggleSwitch = () => ({ type: TOGGLE_SWITCH });
const increase = defference => ({ type: INCREASE, defference});
const decrease = () => ({ type: DECREASE });

액션은 상태에 변화를 일으키는것이다.

1) 액션 이름은 문자열 형태
2) 주로 대문자로 작성
3) 고유해야함

액션 생성함수는 액션 이름을 사용하여 액션 객체를 만든다.

1) 액션 객체는 type 값이 필수
2) 추후 상태를 업데이트 할때 참고하고 싶은 값(필수X)

초깃값 설정(initialState)

초깃값의 형태는 자유다.

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

리듀서 함수 정의

// reducer
function reducer(state = initialState, action) {
    switch (action.type) {
        case TOGGLE_SWITCH: 
            return {
                ...state, 
                toggle: !state.toggle
            };
        case INCREASE: 
            return {
                ...state,
                counter: state.counter + action.defference
            }
        case DECREASE: 
            return {
                ...state,
                counter: state.counter - 1
            }
        default: 
            return state;
    }
}

reducer가 처음 호출될때 state는 undefined인데 그때 initialState를 reducer의 첫번째 parameter인 state의 기본값으로 설정했다.
리듀서에서는 상태의 불변성을 유지하면서 데이터에 변화를 일으켜야한다. spread연산자(···)를 사용하면 편하다. 객체의 구조가 복잡해지면 spread연산자로는 번거로울수 있고 가독성도 나빠지기때문에 리덕스의 상태는 최대한 깊지 않은 구조로 진행하는것이 좋다.
객체의 구조가 복잡하거나 배열도 다룰 경우 immer라는 라이브러리를 사용하면 좀더 쉽게 리듀서를 작성할 수 있다.

스토어 만들기(createStore)

import { createStore } from 'redux';

const store = createStore(reducer);

스토어를 만들때는 createStore함수를 사용하고, 매개변수에는 리듀서 함수를 넣어야한다.


createStore를 import시

createStore

로 보이고 Redux createStore() is deprecated라는 표시가 뜨는데 이부분은 스택오버플로우에 정확한 답변이 나와있다.

stackoverflow - Redux createStore() is deprecated

plain Redux를 사용시에 이 메시지가 표시되고 있고 플레인으로 사용하면 더 사용하기 어려우니 Redux Toolkit을 대신 사용하라고 권장한단 내용인것같다.
런타임경고가 아니라 그냥 에디터에서도 표시가 되는데 그냥 단지 visual indicator일뿐이라고 한다.

어차피.. 이번만 plain Redux로 사용하고 react에서 사용할 예정이니 패스하겠다.

render 함수 만들기

const render = () => {
    const state = store.getState();
    if(state.toggle) {
        divToggle.classList.add('active');
    } else {
        divToggle.classList.remove('active');
    }
    counter.innerText = state.counter;
}

render();

render함수는 리액트의 render함수와 달리 이미 html을 사용하여 만들어진 UI의 속성을 상태에따라 변경해주는 용도이다. 이 함수는 상태가 업데이트될때마다 호출된다.

구독하기(subscribe)

// subscribe
store.subscribe(render);

store의 내장함수 subscribe함수를 사용해 스토어의 상태가 바뀔때마다 render함수가 실행되도록 한다. subscribe의 파라미터는 함수가 들어간다. 전달된 함수는 추후 액션이 발생하여 상태가 업데이트 될 때마다 호출된다.

이번 프로젝트는 subscribe함수를 직접 사용하지만,
리액트프로젝트에서 리덕스를 사용할때는 이 함수를 직접 사용하지 않는다.

컴포넌트에서 리덕스 상태를 조회하는 과정에서 react-redux라는 라이브러리가 이 작업을 대신해주기 때문이다.

액션 발생시키기(dispatch)

// action 발생(dispatch)
divToggle.onClick = () => {
    store.dispatch(toggleSwitch());
}
btnIncrease.onClick = () => {
    store.dispatch(increase(1));
}
btnDecrease.onClick = () => {
    store.dispatch(decrease());
}

스토어의 내장함수 dispatch는 액션을 실행시킨다. 파라미터는 액션객체를 넣는다.

리덕스의 세가지 규칙

  1. 하나의 어플리케이션에는 하나의 스토어가 들어있다. (단이리 스토어)
    특정 업데이트가 빈번하거나 어플리케이션의 특정 부분을 완전 분리시킬때 여러개 스토어를 만들 수도 있지만, 상태관리가 복잡해질수 있으므로 권장하지 않음.
  2. 읽기 전용 상태
    리덕스는 읽기 전용이다.
    기존에 리액트에서 상태를 변화시킬때도, 객체나 배열을 업데이트하는 과정에서 불변성을 지켜주기 위해 spread연산자를 사용하거나 immer와 같은 불변성 관리 라이브러리를 사용했다. 리덕스도 마찬가지이다. 상태를 업데이트할때 기존객체는 건드리지 않고 새로운 객체를 생성해주어야한다.

리덕스에서 불변성을 유지해야 하는 이유
내부적으로 데이터가 변경되는것을 감지하기 위해 얕은 비교검사(shallow equality)를 함.

객체의 변화를 감지할때 객체의 깊숙한 안쪽까지 비교하는 것이 아니라 겉핥기식으로 비교하여 좋은 성능을 유지한다.

  1. 리듀서는 순수함수
    순수함수는 부수효과(side effect)를 일으키지 않고 입력값에대해 항상 동일한 출력값을 반환한다.
    순수함수인 리듀서는 다음 조건을 만족한다.
  • 리듀서함수는 이전 상태와 액션 객체를 파라미터로 받는다.
  • 파라미터 외의 값에는 의존하면 안된다.
  • 이전 상태는 절대 건드리지 않고, 변화를 준 새로운 상태 객체를 만들어서 반환(불변성)
  • 똑같은 파라미터로 호출된 리듀서 함수는 언제나 똑같은 값을 반환한다.

위 네가지 사항에 주의하여 리듀서를 작성해야한다.

리듀서 금지사항 ^^

  • 내부에서 랜덤값을 만들거나,
  • Date함수를 사용해서 현재시간을 가져오거나
  • 네트워크 요청
    을 하면 안된다!!!!!

파라미터가 같아도 다른값을 반환하기때문에.

이러한 작업은 리듀서 바깥에서 처리해줘야한다.
1) 액션을 만드는 과정이나, 2) 리덕스 미들웨어에서 처리해도 된다.
네트워크 요청과 같은 비동기작업은 주로 미들웨어를 통해 관리한다.

정리

리덕스 코드를 작성하는 흐름은 다음과 같다.

  1. 액션타입과 액션 생성함수 작성
  2. 리듀서 작성
  3. 스토어 생성
반응형

최근댓글

최근글

© Copyright 2023 jngmnj