[☂️ Redux] Redux 이해하기

dsfasd·2022년 11월 2일
0

Props Drillig

자바스크립트의 라이브러리인 리액트를 사용할 때,
state라는 변수를 선언하여 사용할 수 있다.

state 변수를 사용하기 위해서는 최상단 컴포넌트에 state 변수를 선언하고,
하위 컴포넌트로 props를 전달하여 사용하는 방식이다.

중첩된 컴포넌트 수가 많아질 수록 props를 계속 전달하여 state를 사용해야 하는데,
이러한 문제점을 props drilling이라고도 한다.

스파게티 코드의 문제점은 가독성과 유지보수가 나빠진다는 것이고,
무엇보다 props drilling으로 인해 불필요한 리렌더링이 계속 발생한다는 점이다.


Redux

불필요한 props drilling을 막기 위해서 사용하면 좋은 것이 redux이다.

redux는 자바스크립트의 상태 관리 라이브러리 중 하나이다.

redux를 이용하면 state 변수들을 전역 저장소 한 곳에 모아둘 수 있다.
이 저장소는 모든 컴포넌트가 접근 가능하므로, state변수를 편리하게 꺼내쓸 수 있다.

또한 직접적으로 state 변수가 사용되는 곳에서만 리렌더링이 되므로 성능 최적화에 도움을 준다.


Redux의 구조

redux의 구조는 store, reducer, action, dispatch 요소로 이뤄진다.

store (상태를 관리하는 저장소)
reducer (상태 변경 함수)
action (어떤 액션을 취할지 정의해놓은 객체)
dispatch (reducer로 action을 전달하는 함수)

Redux의 데이터 흐름 - 단방향

Action객체 생성 → Dispatch 함수 → Reducer 함수 → Store 저장소 -> 렌더링

상태가 변경되는 이벤트가 발생시, 변경될 상태에 대한 정보가 담긴 action 객체가 생성된다.

이 action 객체는 dispatch 함수의 인자로 전달되고,
dispatch 함수는 action 객체를 reducer 함수로 전달해준다.

reducer 함수는 action 객체의 값을 확인 후 전역상태 저장소인 store의 상태를 변경한다.

상태가 변경되면 react는 상태가 변경된 것을 감지하고 화면을 재렌더링한다.

store

상태를 관리하는 저장소 역할을 한다.

import { createStore } from 'redux';

// createStore 메서드를 활용해 Reducer를 연결해서 store를 생성할 수 있다. 
const store = createStore(rootReducer);

Reducer

action 객체의 type에 따라서 상태를 변경시키는 순수함수

순수함수 는 동일한 인자가 주어졌을 때 항상 동일한 결과를 반환해야 하며 외부의 상태를 변경하지 않는 함수이다.

첫번째 인자로는 state의 초기값을, 두번째 인자로는 action을 준다.

// 상태변경은 리듀서에서 진행
const count = 1;

// Reducer를 생성할 때에는 초기 상태를 인자로 요구합니다.
const counterReducer = (state = count, action) => {
  // Action 객체의 type 값에 따라 분기하는 switch 조건문입니다.
  switch (action.type) {
    //action === 'INCREASE'일 경우
    case 'INCREASE':
      return state + 1;

    // action === 'DECREASE'일 경우
    case 'DECREASE':
      return state - 1;

    // action === 'SET_NUMBER'일 경우
    case 'SET_NUMBER':
      return action.payload;

    // 해당 되는 경우가 없을 때
    default:
      return state;
  }
};

// counterReducer가 reducer에 해당한다. 
const store = createStore(counterReducer);

root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

action

어떤 액션을 취할 것인지 정의해 놓은 객체


// increase와 decrease는 action 객체이다. 
// action create 함수를 다른 파일에도 사용하기 위해 export 해준다. 
const increase = () => {
  return {
    type: 'INCREASE'
  }
}
export default increase

const decrease = () => {
  return {
    type: 'DECREASE'
  }
}
export default decrease


const count = 1;

const counterReducer = (state = count, action) => {
  switch (action.type) {
      
    case 'INCREASE':
      return state + 1;

    case 'DECREASE':
      return state - 1;

    case 'SET_NUMBER':
      return action.payload;

    default:
      return state;
  }
};

const store = createStore(counterReducer);

root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

dispatch

reducer로 action을 전달해주는 함수

dispatch의 전달 인자로 action 객체를 준다.

// Action 객체를 직접 작성하는 경우
dispatch( { type: 'INCREASE' } );
dispatch( { type: 'SET_NUMBER', payload: 5 } );

// 액션 생성자(Action Creator)를 사용하는 경우
dispatch( increase() );
dispatch( setNumber(5) );

Redux state를 이용한 예제

+,- 버튼을 누를 때마다 count가 증가하는 예제이다.
index 파일에는 결론적으로 store, reducer, action 기능이 담겨있다.

생성 과정은 store 생성-> reducder 작성 -> action 작성으로 이뤄진다.

index.js

import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import { legacy_createStore as createStore } from 'redux';

const rootElement = document.getElementById('root');
const root = createRoot(rootElement);

// 3. action 함수이다. 
// 어떤 함수를 호출할때 어떤 action을 줄지 명세한다. 
// 아래 2번에서 action.type이 INCREASE인 경우 state+=1이 되는데
// 결과적으로는 increase 함수를 실행시키는 경우 INCREASE 액션이 작동된다. 
  
export const increase = () => {
  return {
    type: 'INCREASE',
  };
};

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

  
// 2. counterReducer - 상태 변경 함수이다.  
// createStore의 인자로 준 함수에 해당한다.  
  
const count = 1;
// Reducer를 생성할 때에는 초기 상태를 인자로 
// 요구하므로 count 변수를 선언, 초기화 한 후 담아준다. 
  
// Reducer( state, action )  
const counterReducer = (state = count, action) => {
  
  // Action 객체의 type 값에 따라 분기한다. 
  switch (action.type) {
    //action === 'INCREASE'일 경우
    case 'INCREASE':
      return state + 1;

    // action === 'DECREASE'일 경우
    case 'DECREASE':
      return state - 1;

    // action === 'SET_NUMBER'일 경우
    case 'SET_NUMBER':
      return action.payload;

    // 해당 되는 경우가 없을 땐 기존 상태를 그대로 리턴한다. 
    default:
      return state;
  }
  // Reducer가 리턴하는 값이 변경되는 상태이다. 
};

  
// 1. createStore를 이용하여 전역 변수 저장소를 생성해준다. 
// createStore의 인자로 counterReducer를 전달해준다. 
const store = createStore(counterReducer);

// App컴포넌트를 Provider컴포넌트로 감싸고
// 전역변수 저장소를 props로 내려준다. 
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

이미 index.js 파일에서 상태 저장소도 생성했고,
어떤 함수가 실행될 때 어떤 action이 동작할 지도 작성해두었다.

App.js 파일에는 redux hooks인 useDispatch()useSelector()를 사용하여 직접적으로 저장소에서 state를 꺼내 사용하는 과정을 볼 수 있다.

useDispatch( ) 는 Action 객체를 Reducer로 전달해 주는 Dispatch 함수를 반환하는 메서드이고,

useSelector( )는 컴포넌트와 state를 연결하여 Redux의 state에 접근할 수 있게 해주는 메서드이다.

App.js

import React from 'react';
import './style.css';
import { useDispatch,useSelector } from 'react-redux';
import { increase, decrease } from './index.js';

export default function App() {
  const dispatch = useDispatch();
   const state = useSelector((state) => state);
   console.log(state);

 // 4. dispatch
 // dispatch 함수의 인자로 action을 전달해준다. 
 // 이전에 작성한 increase, decrease action을 
 // 아래 onClick과 연결된 이벤트 함수에 전달해주고 있다. 
  
  const plusNum = () => {
    dispatch(increase());
  };

  const minusNum = () => {
    dispatch(decrease());
  };

  
 // useSelectort 메서드를 통해 생성한 state 변수를 사용할 수 있다. 
  return (
    <div className="container">
      <h1>{`Count: ${state}`}</h1>
      <div>
        <button className="plusBtn" onClick={plusNum}>
          +
        </button>
        <button className="minusBtn" onClick={minusNum}>
          -
        </button>
      </div>
    </div>
  );
}
   
profile
기록을 정리하는 공간!

0개의 댓글