React.js - redux1(리덕스 기본 개념)

Gyu·2022년 7월 4일
0

React.js

목록 보기
14/20
post-thumbnail

리덕스란?

  • 가장 많이 사용하는 리액트 상태관리 라이브러리
  • 리덕스를 사용하면 컴포넌트의 상태 업데이트 로직을 다른 파일로 분리시켜 더욱 효율적으로 관리할 수 있다.
  • 전역 상태를 관리할 때 굉장히 효과적. 단순히 전역상태 관리만 한다면 Context API를 사용하는 것으로도 충분. 하지만 프로젝트 규모가 클 경우 리덕스를 사용하는 것이 좋다. 왜냐하면 리덕스를 사용하면 상태를 더욱 체계적으로 관리할 수 있고 유지보수성을 높여주고 작업 효울도 극대화 시켜주기 때문이다.
  • 추가로 편리한 개발자 도구를 지원하며 미들웨어라는 기능을 제공하여 비동이 작업을 효율적으로 관리할 수 있게 해준다.

리덕스 개념 미리 정리하기

액션(action)

  • 상태에 어떤 변화가 필요하면 액션이 발생한다. 액션은 하나의 객체로 표현된다.
  • 액션은 type 필드를 갖고 있는 자바스크립트 객체로, 어플리케이션에서 일어난 이벤트에 대한 설명이라고 생각할 수 있다. type 필드는 액션을 설명하는 이름을 지정하는 문자열이어야 한다.
  • type 값은 주로 domain/eventName 형식으로 작성한다. 첫 번째 부분에는 특징이나 액션이 속한 카테고리를, 두 번쨰 부분에는 상세하게 일어난 일을 적는다.
  • 액션 객체는 다른 필드를 가질 수 있는데, 이는 상태를 업데이트 할 때 참고할 값으로 사용한다. 관례적으로 이를 payload라고 부른다.
  • 액션 예시
    const addTodoAction = {
      type: 'todos/todoAdded',
      payload: 'Buy milk' // payload
    }

액션 생성 함수(action creator)

  • 액션 생성 함수는 액션 객체를 만들어 주는 함수다.
  • 어떤 변화를 일으킬 때마다 액션 객체를 만들어야 하는데, 매번 액션 객체를 직접 작성하기 번거롭고 만드는 과정에서 실수로 정보를 놓칠 수 있기 때문에 이를 방지하기 위해 함수로 만들어 관리한다.
  • 에시
    function addTodo(data){
    	return {
    		type: 'ADD_TODO',
    		data
    	}
    }
    
    // Arrow function
    const addTodo = data => {
    	type: 'ADD_TODO',
    	data
    }

리듀서(reducer)

  • 리듀서는 변화를 일으키는 함수다. 액션을 만들어 발생시키면 리듀서가 현재 상태와 전달받은 액션 객체를 파라미터로 받아온다. 그리고 두 값을 참고하여 새로운 상태를 만들어 반환해준다.
  • 리듀서는 현재 상태와 액션을 매개변수로 전달받아 어떻게 상태를 변경할지 결정하고, 새로운 상태를 반환하는 함수다. 매개변수로 전달받은 액션의 type을 기준으로 이벤트를 처리하는 이벤트 리스너라고 생각할 수 있다.
  • 리듀서는 전형적으로 아래의 일련 과정을 따른다.
    • 해당 리듀서가 해당 액션을 처리하는지 확인
      • 만약 그렇다면 매개변수로 전달받은 상태를 복사 → 복사한 상태 업데이트 → 반환
    • 만약 아니라면 매개변수로 전달받은 상태를 그대로 반환
  • 예시
    const initialState = { counter: 1 };
    
    function reducer(state = initialState, action){
    	// 리듀서가 해당 액션을 처리하는지 확인
    	switch (action.type) {
    		case INCREMENT: 
    			return { // 상태 복사 및 업데이트 -> 반환
    				...state, 
    				counter: state.counter + 1 
    			};
    		default: // 리듀서가 해당 액션 처리 담당이 아니면 전달받은 상태 반환
    			return state;
    	}
    }

스토어(store)

  • 프로젝트에 리덕스를 적용하기 위한 스토어를 만든다. 하나의 프로젝트는 단 하나의 스토어만 가질 수 있다. 스토어 안에는 현재 애플리케이션 상태와 리듀서가 들어가 있으며, 그 외에도 몇 가지 중요한 내장 함수를 지닌다.
  • 스토어는 리듀서 함수로 생성된다. 리덕스 스토어는 액션이 디스패치 될 때마다 루트 리듀서를 실행시킨다.

디스패치(dispatch)

  • 디스패치는 스토어 내장 함수 중 하나로 액션을 발생 시키는 역할을 한다. 이 함수는 dispatch(action) 형태로 액션 객체를 파라미터로 넣어서 호출한다.
  • 이 함수가 호출되면 리듀서 함수를 실행시켜 새로운 상태를 만들어준다. 상태를 변경하는 유일한 방법은 디스패치 함수를 사용하는 것이다.

구독(subscribe)

  • 구독도 스토어 내장 함수 중 하나다. subscribe 함수 안에 리스너 함수를 파라미터로 넣어서 호출해 주면, 이 리스터 함수가 액션이 디스패치되어 상태가 업데이트될 때마다 호출된다.
  • 예시
    const listener = () => {
    	console.log('상태가 업데이트 됨');
    }
    
    const unsubscribe = store.subscribe(listener);
    
    // 추후 구독을 비활성할 때 함수를 호출
    unsubscribe();

바닐라 자바스크립트로 리덕스 사용하기

  • 리덕스는 리액트에 종속되는 라이브러리가 아니다. 리액트에서 사용하려고 만들어졌지만 다른 UI 라이브러리/프레임워크와 함께 사용할 수 있다.
  • 예제를 통해 바닐라 자바스크립트 환경에서 리덕스를 사용하여 리덕스의 핵심 기능화 작동원리를 알아보자.

개발환경 세팅

  • 리액트 없이 번들링하기 위해 Parcel를 번들링 툴로 사용
mkdir vanilla-redux
cd vanilla-redux
npm init -y
npm i -g parcel-bundler
npm i redux

코드

import { createStore } from "redux";

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

// 2. 액션 타입(이름) 정의
// 액션 이름은 문자열 형태로, 주로 대문자로 작성하며
// 액션 이름은 고유해야한다.
const TOGGLE_SWITCH = 'TOGGLE_SWITCH',
    INCREASE = 'INCREASE',
    DECREASE = 'DECREASE';

// 3. 액션 이름을 사용하여 액션 객체를 만드는 액션 생성 함수 작성
// 액션 객체는 반드시 type 값을 갖고 잊어야 하며
// 참고할 값은 개발자가 원하는데로 작성 가능
const toggleSwitch = () => ({type: TOGGLE_SWITCH});
const increase = difference => ({type: INCREASE, difference});
const decrease = () => ({type: DECREASE});

// 4. 초깃값 설정
const initialState = {
    toggle: false,
    counter: 0
};

// 5. 리듀서 함수 정의
// 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;
    }
}

// 6. 스토어 함수 만들기
// 스토어를 만들 때는 createStore 함수를 사용한다.
// 최상단에서 import 구문으로 createStore 함수를 불러와야한다.
// reateStore 함수의 파라미터에 리듀서 함수를 넣어줘야 한다.
const store = createStore(reducer);

// 7. render 함수 생성
// 상태가 업데이트 될 때마다 호출
// 리액트의 render함수와 다르게 직접 DOM 조작
const render = () => {
    const state = store.getState(); // 현재 상태를 불러온다.

    // 토글처리
    if (state.toggle) {
        divToggle.classList.add('active');
    } else {
        divToggle.classList.remove('active');
    }

    // 카운터 처리
    counter.innerText = state.counter;
}

render();

// 8. 구독하기
// 실제 리액트 프로젝트에서는 사용하지 않음
// 컴포넌트에서 리덕스 상태를 조회하는 과정에서 
// react-redux라는 라이브러리가 이 기능을 대신 하기 때문
store.subscribe(render); // 상태변경시 render 함수 호출

// 액션 발생 시키기
divToggle.onclick = () => {
    store.dispatch(toggleSwitch());
}
btnIncrease.onclick = () => {
    store.dispatch(increase(1));
}
btnDecrease.onclick = () => {
    store.dispatch(decrease());
}

리덕스 데이터 흐름

초기 생성

  • 루트 리듀서 함수를 사용하여 리덕스 저장소 생성
  • 저장소가 루트 리듀서 함수를 한번 호출하고 반환값을 초기 상태값으로 저장한다.
  • UI가 처음 렌더링 될 때, UI 컴포넌트는 리덕스 저장소의 현재 상태에 접근한다. 그리고 그 데이터를 사용하여 화면을 그린다. 컴포넌트들은 또한 미래 스토어 변경을 구독하여 상태 변경을 알 수 있다.

업데이트

  • 클릭 같은 이벤트 발생
  • 앱은 dispatch({type: 'counter/increment'}) 형식으로 Redux 스토어에게 액션을 보냄
  • 스토어는 이전 상태와 현재 상태로 리듀서 함수를 작동 시킴. 그리고 반환 값을 새 상태로 저장한다.
  • 스토어는 구독되고 있는 UI의 모든 부분에 스토어가 변경되었음을 알린다.
  • 스토어의 데이터가 필요한 각 UI 컴포넌트는 필요는 상태의 일부가 변경되었는지 확인한다.
  • 데이터 변경을 감지한 각 컴포넌트는 새로운 데이터로 리렌더링하여 사용자에게 새로운 화면을 보여준다.

리덕스 3가지 규칙

1. 단일 스토어

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

2. 읽기 전용 상태

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

3. 리듀서는 순수함수

  • 리듀서는 순수한 함수여야 하며, 순수함수는 다음 조건을 만족시킨다.
    • 리듀서 함수는 이전 상태와 액션 객체를 파라미터로 받는다.
    • 파라미터 외에 값에는 의존하면 안된다.
    • 이전 상태는 절대로 건드리지 않고, 변화를 준 새로운 상태 객체를 만들어 반환한다.
    • 똑같은 파라미터로 호출된 리듀서 함수는 언제나 똑같은 결과값을 반환해야한다.
  • 순수 함수란 동일한 입력을 받았을 때 언제나 동일한 출력을 내는 함수를 말한다. 클릭 시 배경이 랜덤으로 바뀌는 로직을 작성한다면, 함수의 로직 내에서 랜덤값을 생성해야한다. 랜덤 값을 생성한다는 뜻은 결국 매번 출력 값이 바뀐다는 뜻이기 때문에 순수하지 못한 함수다. 네트워트 요청을 하는 작업도 순수하지 못한 로직 중 하나다.
profile
애기 프론트 엔드 개발자

0개의 댓글