ReactJS-16 리덕스 라이브러리

  • 리덕스는 가장 많이 사용하는 리액트 상태 관리 라이브러리이다.

1. 개념 정리

1-1) 액션

  • 상태에 어떠한 변화가 필요하면 액션(action) 이란 것이 발생
  • 액션 객체는 type 필드를 반드시 가지고 있어야 한다.
{
    type: 'ADD_TODO',
    data: {
        id: 1,
        text: '리덕스 배우기'
    }
},
{
    type: 'CHANGE_INPUT',
    text: '안녕하세요'
}

1-2) 액션 생성 함수

  • 액션 객체를 만들어 주는 함수
function addTodo(data) {
    return {
        type: 'ADD_TODO',
        data
    };
}

// 화살표 함수로도 가능
const changeInput = text => ({
    type: 'CHANGE_INPUT',
    text
});

1-3) 리듀서(reducer)

  • 변화를 일으키는 함수
  • 액션을 만들어서 발생시키면 리듀서가 현재 상태와 전달 받은 액션객체를 파라미터로 받아 온다.
  • 그리고 두 값을 참고하여 새로운 상태를 만들어서 반환해 준다.
const initialState = {
    counter: 1
};
function reducer(state = initialState, action) {
    switch (action.type) {
        case INCREMENT:
        	return {
                counter: state.counter + 1
            };
        default:
            return state;
    }
}

1-4) 스토어(store)

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

1-5) 디스패치(dispatch)

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

1-6) 구독(subscribe)

  • 스토어의 내장 함수 중 하나이다.
  • subscribe 함수 안에 리스너 함수를 파라미터로 넣어서 호출하면, 이 리스너 함수가 액션이 디스패치 되어 상태가 업데이트 될때마다 호출된다.
const listener = () => {
    console.log('상태가 업데이트됨');
}
const unsubscribe = store.subscribe(listener);

unsubscribe();

2. 리액트 없이 쓰는 리덕스

  • 리덕스는 리액트에 종속되는 라이브러리가 아니다.
  • 리덕스는 바닐라 자바스크립트와 함께 사용할 수 있다.
  • 바닐라 자바스크립트는 라이브러리나 프레임워크 없이 사용하는 순수 자바스크립트 그 자체를 의미

2-1) Parcel로 프로젝트 만들기

$ yarn global add parcel-bundler
# yarn global 설치가 안된다면 npm install -g parcel-bundler
$ mkdir banilla-redux
$ cd vanilla-redux
# package.json 파일 생성
$ yarn init -y
  • 디렉터리 열어서 index.html, index.js 파일 생성
// index.html
<html>
    <body>
        <div>바닐라 자바스크립트</div>
        <script src="./index.js"></script>
    </body>
</html>
// index.js
console.log('hello parcel');
  • 개발용 서버 실행
    • localhost:1234
$ parcel index.html

reactjs-16-01

  • 리덕스 모듈 설치

    • $ yarn add redux
      

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;
}
<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>

reactjs-16-02

2-3) DOM 레퍼런스 만들기

  • UI를 관리할 때 별도의 라이브러리를 사용하지 않기 때문에 DOM을 직접 수정해 주어야 한다.
// index.js
const divToggle = document.querySelector('.toggle');
const counter = document.querySelector('h1');
const btnIncrease = document.querySelector('#increase');
const btnDecrease = document.querySelector('#decrease');

2-4) 액션 타입과 액션 생성 함수 정의

  • 프로젝트의 상태에 변화를 일으키는 것을 액션
// 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 });

2-5) 초깃값 설정

  • 초깃값을 정의, 초깃값의 형태는 자유
// index.js
...

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

2-6) 리듀서 함수 정의

  • 리듀서는 변화를 일으키는 함수
  • 파라미터로는 state와 action 값을 받아온다.
// index.js
...

// state가 undefined일 때는 initialState를 기본값으로 사용
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;
    }
}
  • 리듀서 함수가 맨 처음 호출될 때는 state 값이 undefined 이다.
  • 해당 값이 undefined 로 주어졌을 때는 initialState 를 기본값으로 설정하기 위해 함수의 파라미터 쪽에 기본값이 설정되어 있다.

2-7) 스토어 만들기

  • createStore 함수 사용
  • 함수의 파라미터에는 리듀서 함수를 넣어준다.
// index.js
...

import { createStore  } from "redux";

...
const store = createStore(reducer)

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;
};

render();

2-9) 구독하기

  • 스토어의 상태가 바뀔 때마다 render 함수가 호출되도록 해줘야한다.
  • 스토어의 내장 함수 subscribe를 사용하여 수행한다.
  • subscribe 함수의 파라미터로는 함수 형태의 값을 전달해 주고, 추후 액션이 발생하여 상태가 업데이트될 때마다 호출된다.
// 예시코드
const listener = () => {
	console.log('상태가 업데이트됨');
}
const unsubscribe = store.subscribe(listener);

unsubscribe();	// 추후 구독을 비활성화할 때 함수를 호출
  • 추후 리액트 프로젝트에서 리덕스를 사용할 때는 이 함수를 직접 사용하지 않는다.
  • 컴포넌트에서 리덕스 상태를 조회하는 과정에서 react-redux 라는 라이브러리가 이 작업을 대신해 준다.
// index.js
...
render();
store.subscribe(render);

2-10) 액션 발생시키기

  • 액션을 발생시키는 것을 디스패치
  • 스토어의 내장 함수 dispatch 를 사용하고, 파라미터는 액션 객체를 넣어준다.
// index.js
...

divToggle.onclick = () => {
    store.dispatch(toggleSwitch());
};
btnIncrease.onclick = () => {
    store.dispatch(increase(1));
};
btnDecrease.onclick = () => {
    store.dispatch(decrease());
};

reactjs-16-03

3. 리덕스의 세 가지 규칙

3-1) 단일 스토어

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

3-2) 읽기 전용 상태

  • 리덕스 상태는 읽기 전용이다.
  • 상태를 업데이트할 때 기존의 객체는 건드리지 않고 새로운 객체를 생성해 주어야 한다.
  • 리덕스에서 불변성을 유지해야 하는 이유는 내부적으로 데이터가 변경되는 것을 감지하기 위해 얕은 비교 검사를 하기 때문이다.

3-3) 리듀서는 순수한 함수

  • 변화를 일으키는 리듀서 함수는 순수한 함수여야 한다.
    • 리듀서 함수는 이전 상태와 액션 객체를 파라미터로 받는다.
    • 파라미터 외의 값에는 의존하면 안된다.