[React] - Redux

Lee Jeong Min·2022년 1월 5일
0
post-thumbnail

React Redux

상태 관리의 필요성

JS 앱의 상태 관리가 복잡해진만큼 버그를 최소화 하고, 잘 만들어진 사용자 경험(UX)을 제공하는 데 있어 상태를 효과적으로 관리할 필요성이 생겨났다.
여기서 말하는 상태란? 서버 응답, 캐시 데이터, 로컬 상태(아직 서버에 저장되지 않은 데이터) 등을 의미한다. 뿐만 아니라 활성화 된 라우트, 선택된 탭 핸들, 로딩 표시 여부, 페이지네이션 컨트롤 등 다양한 UI View 상태도 해당된다.이러한 복잡성은 오늘 날 앱 제작에 있어 가장 어려운 부분 중 하나이다.

참고 사이트: https://redux.js.org/understanding/thinking-in-redux/motivation

Redux의 특징

  • 예측 가능
  • 중앙 관리
  • 디버깅 용이
  • 유연한 확장성

Redux는 언제 사용?

  • 지속적으로 업데이트 되는 상당한 양의 상태가 있다.
  • 상태 업데이트를 위한 단 1개의 스토어가 필요하다.
  • 최상위 루트 컴포넌트가 모든 상태를 관리하는 것은 적절하지 않다.

앱의 규모가 작은 경우 React만으로 상태 관리하는 방법을 사용하는 것이 더 효과적일 수 있다.

Redux의 3원칙

  1. 애플리케이션 상태는 모두 한 곳에서 관리된다. (동기화 필요 X, 디버깅 용이)

  2. 상태는 불변(읽기 전용) 데이터이며 오직 액션을 전달해 디스패치하는 것만이 상태를 업데이트 할 수 있다.(예측 가능)

  3. 리듀서(순수 함수)를 사용해 상태를 다음 상태로 업데이트 한다.(순수함울 보장)

Redux 아키텍처

Redux는 Store, State, Reducer, Action, Subscription 등을 제공해 효율적으로 상태를 관리한다.

Redux js 실습(꼭 react에서만 사용하는 것은 아님)

index.html

<!DOCTYPE html>
<html lang="ko-KR">
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Redux 아키텍처</title>
  <link rel="stylesheet" href="./css/main.css" />
  <script type="module" src="./js/main.js"></script>
</head>
<body>

  <pre></pre>
  
  <div class="buttonGroup">
    <button type="button" class="button" onclick="moveLeft(-5)">move Left</button>
    <button type="button" class="button" onclick="moveRight(5)">move Right</button>
    <button type="button" class="button" onclick="randomBallColor()">Random Change Ball Color</button>
  </div>

  <div class="circle"></div>

</body>
</html>

js/main.js

// css variable get/set
// import like redux lib.
import { cssVars } from '../lib/cssvar.js';
import { createStore } from '../lib/likeRedux.js';

// state 매개변수는 초깃값을 전달 받아 설정
const initialState = {
  color: cssVars('--color'),
  x: 50, // %,
  y: 50,
};

// 리듀서 (순수)함수: 상태 업데이트
// 매개변수: state, action
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'MOVE_DOWM':
    case 'MOVE_UP':
      return {
        ...state,
        y: state.y + action.payload,
      };
    case 'MOVE_RIGHT':
    case 'MOVE_LEFT':
      return {
        ...state,
        x: state.x + action.payload,
      };
    case 'CHANGE_RANDOM_BALL_COLOR':
      return {
        ...state,
        color: action.payload,
      };

    default:
      return state;
  }
};

// 스토어 생성하기 (<- 리듀서 함수)
const store = createStore(reducer);

// 스토어를 구독할 함수
function render() {
  const state = store.getState();
}

// 스토어의 구독 메서드를 사용해 구독할 함수를 구독 설정
store.subscribe(render);

// 액션 (정보 객체 : 무엇을 수행해서 상태를 업데이트 할 것인가?)
// 스토어를 통해 사용자가 요구하는 액션을 디스패치 하기
// store.dispatch({
//   type: 'MOVE_UP',
// });
// store.dispatch({
//   type: 'MOVE_DOWM',
// });
// store.dispatch({
//   type: 'MOVE_LEFT',
// });
// store.dispatch({
//   type: 'MOVE_RIGHT',
// });

// 컨트롤 할 DOM 요소노드
const ball = document.querySelector('.circle');
const output = document.querySelector('pre');

// 구독할 함수 작성
function printState() {
  const state = store.getState();
  output.textContent = JSON.stringify(state, null, 2);
}

printState();

function moveBall() {
  // 상태 가져오기
  const { x, y } = store.getState();
  ball.style.cssText = `left: ${x}%; top: ${y}%`;
}

function updateBallColor() {
  // 상태 가져오기
  const { color } = store.getState();
  ball.style.background = color;
}

function changeRootNodeCssVar() {
  const { color } = store.getState();
  cssVars('--color', color);
}

// 함수에서 스토어 상태 업데이트를 구독
store.subscribe(moveBall);
store.subscribe(updateBallColor);
store.subscribe(changeRootNodeCssVar);
store.subscribe(printState);

// moveLeft, moveRight 함수 작성
window.moveLeft = function moveLeft(disX) {
  // 상태 업데이트 요청
  store.dispatch({ type: 'MOVE_LEFT', payload: disX });
};

window.moveRight = function moveRight(disX) {
  // 상태 업데이트 요청
  store.dispatch({ type: 'MOVE_RIGHT', payload: disX });
};

window.randomBallColor = function randomBallColor() {
  //RGB -> Hexcode
  let red = Math.ceil(Math.random() * 255).toString(16);
  let green = Math.ceil(Math.random() * 255).toString(16);
  let blue = Math.ceil(Math.random() * 255).toString(16);

  let colorValue = `#${red}${green}${blue}`;
  // 새로운 상태로 업데이트 요청
  store.dispatch({
    type: 'CHANGE_RANDOM_BALL_COLOR',
    payload: colorValue,
  });
};

lib/cssvar.js

// 루트 요소
const rootNode = document.documentElement;

// 글로벌 css 변수 가져오기
function getCssVar(varName) {
  return window, getComputedStyle(rootNode, null).getPropertyValue(varName);
}
// 글로벌 css 변수 설정하기
function setCssVar(varName, value) {
  rootNode.style.setProperty(varName, value);
}

// 가져오거나, 설정하는 유틸리티 내보내기
export const cssVars = (varName, value) => {
  if (!value) {
    return getCssVar(varName);
  } else {
    setCssVar(varName, value);
  }
};

lib/likeRedux.js

export const createStore = reducer => {
  if (typeof reducer !== 'function') {
    throw new Error('createStore 함수는 reducer 함수를 전달 받아야 한다.');
  }
  // 외부에서 접근이 불가능한 상태
  let state = reducer(undefined, {});

  // 외부에서 스토어를 구독하는 함수의 집합 관리
  let listeners = [];

  // 외부에서 상태를 가져오고자 할 때 사용하는 멧더ㅡ
  const getState = () => state;

  // 외부에서 사용자가 액션을 받아, 리듀서 함수에 전달한다.
  const dispatch = action => {
    // 리듀서 함수를 실행해 새로운 상태를 반환
    state = reducer(state, action);

    // 스토어의 상태를 구독중인 리스너 실행
    listeners.forEach(listener => listener?.());
  };

  // 외부의 함수가 스토어의 상태 업데이트를 구독
  // 구독 = 상태 업데이트 감지되면, 함수 실행
  const subscribe = addListener => {
    // 구독
    listeners.push(addListener);

    // 구독 해지 (cleanup)
    return () => {
      listeners = listeners.filter(listener => listener !== addListener);
    };
  };

  // 스토어 객체 반환
  return {
    getState,
    dispatch,
    subscribe,
  };
};

리액트에서 리덕스 사용

패키지 설치

yarn add redux
yarn add react-redux

redux devtool 설치

https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd/related?hl=ko

리듀서에 아래의 코드 추가

참고사이트: https://github.com/zalmoxisus/redux-devtools-extension#11-basic-store

window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()

또는 패키지를 설치

yarn add -D redux-devtools-extension

이후

store/index.js

import { composeWithDevTools } from 'redux-devtools-extension';

...
// 스토어 생성
export const store = createStore(rootReducer, composeWithDevTools());

constate 라이브러리의 경우 하나의 상태를 관리할 때 유용하게 쓰이지만 여러개를 관리하는 경우 provider를 여러개 만들어야하기 때문에 Redux와 같은 라이브러리들을 사용하게 된다.

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글

관련 채용 정보