#1. Redux

1. Redux의 기본 개념

1) Redux란?

  • 리액트의 전역 상태 관리 라이브러리 이다.
  • 리액트로 일반적인 컴포넌트 개발시에는 상태값(변수)을 관리하기 위해 라이프사이클이나 hook을 사용한다.
    • 이 경우 각각의 컴포넌트가 관리하는 변수값들이 소스파일 여기저기에 흩어져 있기 때문에 코드 유지보수에 좋지 않다.
  • 하지만 Redux를 사용하면 컴포넌트의 상태 업데이트 관련 로직을 다른 파일로 분리시켜서 더욱 효율적으로 관리할 수 있다.
  • 즉, 여러개의 컴포넌트가 개별적으로 관리하는 상태값들을 하나의 소스에 모아 놓고 통합 관리하는 것이 목적이다.
  • 컴포넌트 끼리 상태를 고유해야 할 때도 여러 컴포넌트를 거치지 않고 손쉽게 상태 값을 전달하거나 업데이트 할 수 있다.

 

2) Redux의 기본 요소

  • 상태값
  • 액션 - 문자열
  • 액션함수 - 리듀서 호출 함수
  • 리듀서 - 상태값 갱신 함수
  • 스토어 - 상태값 저장소
  • 구독 - 상태값이 변경되었음을 감지하는 기능
  • 디스패치 - 액션함수 호출

 

3) 패키지 설치

- react-redux

yarn add react-redux
  • 리액트에서 Redux를 사용할 수 있도록 해주는 컨테이너
  • Redux에 의존한다.

 

- @reduxjs/toolkit

yarn add @reduxjs/toolkit
  • 리액트에서 리덕스를 좀 더 간결하게 사용할 수 있도록 하는 패키지

 

- redux-logger

yarn add redux-logger
  • redux가 관리하는 상태값을 자동으로 로그에 표시하는 미들웨어

 

- redux-devtools-extension

yarn add redux-devtools-extension
  • redux의 상태를 크롬브라우저 개발자도구에 설치된 확장 기능과 연동할 수 있게 해주는 미들웨어

 

※ yarn 에서 패키지 여러개 설치할 때는 띄어쓰기로 구분한다.

yarn add react-redux @reduxjs/toolkit redux-logger redux-devtools-extension

 

2. 리덕스 미들웨어

  • 액션을 디스패치 했을 때 (slice의 저장되어 있는 함수를 호출, 흔히 CRUD) 리듀서에서 이를 처리하기에 앞어 실행되는 사전에 지정된 작업들을 말한다.
    • ajax를 호출했을 때, ajax가 동작하기 전에 혹은 동작한 후에 자동으로 수행되게 만들어 놓은 작업들
  • 미들웨어는 index.js에서 스토어를 생성하는 과정에서 적용한다.

 

1) 미들웨어로 수행하는 처리들

  • 전달 받은 액션을 단순히 콘솔에 기록
  • 전달받은 액션 정보를 기반으로 액션을 취소
  • 다른 종류의 액션을 추가로 패치

 

2) 동작 순서

[사용자 이벤트] → [액션]  → [미들웨어] → [리듀서(ajax를 실질적을 동작시킴)  → [스토어](상태값 저장소)

 

3) 미들웨어 종류

  • redux-logger: 브라우저 콘솔에 브라우저의 흐름을 자동으로 기록해준다.
    • ajax가 실행될 때 어떤 함수가 실행되는지 자동으로 출력, ajax가 받아오는 응답결과를 콘솔에 자동으로 찍어준다.
  • redux-devtools-extension

 

3. React에 Redux 적용하기

  • 순정 Redux를 React에 적용하는 것은 매우 복잡한 처리를 요구하기 때문에 최신 버전에서는 Redux-Toolkit 이라는 라이브러리를 통하여 Redux 구조를 단순화 하고 있다.

1) Redux Store 준비하기

  • 상태값, 액션, 액션함수, 리듀서, 스토어가 통합된 형태.
  • 상태값을 관리한다.

- src/store.jsx

// store의 기본 구조

import { configureStore } from '@reduxjs/toolkit';

const store = configureStore({  // configureStore 호출

  // 개발자가 직접 작성한 Slice 오브젝트들이 명시되어야 한다.
  // reducer에 이름과 객체를 나열한다.
  reducer: {
    name: object,
    name: object,
    ...
  }
});

export default store;
  • reduxjs/toolkit에서 configureStore(스토어를 설정하는 함수) 가져온다.
  • configureStore 안에 상태값들을 나열하고 store라는 객체를 export한다.

 

2) React Store를 React에 구독시키기

- src / index.js

// 리덕스 구성을 위한 참조

import { Provider } from 'react-redux';
import store from './store';
  • import한 store에는 store.jsx의 reducer 안에 상태값들이 들어있다.
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </Provider>
  </React.StrictMode>
);
  • 렌더링 처리를 <Provider store={store}> 태그로 감싸준다.
    • store의 상태값이 <App />을 통해서 모든 하위 컴포넌트에 전달된다. (하나의 상태값을 화면이 바뀌어도 사용가능!)

 

3) Slice 모듈 작성

  • 액션값을 갱신하기 위한 실질적인 함수를 만든다.
  • url 하나당 CRUD(입력,수정,삭제,조회)를 담당하는데, url 하나당 slice 하나 라고 보면 된다.

- src/slices/MySlice.jsx

import { createSlice } from '@reduxjs/toolkit';

const slice이름 = createSlice({
  name: 'slice별칭',
  initialState: {
    // 이 모듈이 관리하고자 하는 상태값들을 명시
    변수1: 값,
    변수2: 값
  },

  reducers: {
    // 상태값을 갱신하기 위한 함수들을 구현
    // 컴포넌트에서 이 함수들을 호출할 때 전달되는 파라미터는 action.payload로 전달된다.

    // initialState와 동일한 구조의 JSON을 리턴한다.
    액션함수1: (state, action) => {...state},
    액션함수2: (state, action) => {...state}
  },
});

// 액션함수들 내보내기
export const { 액션함수1, 액션함수2 } = slice이름.actions;

// 리듀서 객체 내보내기
export default slice이름.reducer;
  • initialState에 관리하고자 하는 상태값들을 명시한다.
  • reducers는 상태값을 useState가 아닌 useReducer로 관리한다.
    • 안에 함수들을 구현, state에는 initialState 값이 들어오고, 액션값으로 상태를 리턴 받아 다시 initialState로 보낸다.
  • slice이름. actions로  내보내기하면 reducers의 액션 함수들이 export  된다.

 

상태값을 리턴 받을 때는 action을 사용하고, export할 때는 actions를 사용함에 주의! (뒤에 s가 있고 없고 차이)

 상태값을 갱신할 때는 reducers 를 사용하고, export할 때는 reducer를 사용함에 주의! (뒤에 s가 있고 없고 차이)

 

- src/store.jsx

  • 정의한 Slice 모듈 명시
import { configureStore } from '@reduxjs/toolkit';

import slice이름 from './slice/MySlice';

const store = configureStore({  // configureStore 호출

  // 개발자가 직접 작성한 Slice 오브젝트들이 명시되어야 한다.
  // reducer에 이름과 객체를 나열한다.
  reducer: {
    slice별칭: slice이름,
  }
});

export default store;
  • MySlice.jsx에서 작성한 별칭을 KEY로 사용하고, 객체 이름을 값으로 지정한다.

 

4) 컴포넌트 사용하기

- 필요한 기능 참조

// 상태값을 로드하기 위한 hook과 action함수를 dispatch할 hook 참조
import { useSelector, useDispatch } from 'react-redux';

// Slice에 정의된 액션함수들 참조
import { 액션함수1, 액션함수2 } from '../slice.MySlice';

 

- 컴포넌트 내부에서 hook을 통해 필요한 Object 생성

// hook을 통해 slice가 관리하는 상태값 가져오기
const { 변수1, 변수2 } = useSelector((state) => state.sclice별칭);

// dispatch 함수 행성
const dispatch = useDispatch();
  • useSelector를 사용해서 state를 콜백으로 받고, MySlice.jsx의 slice별칭을 지정하면, initialState에 지정한 변수값을 가져올 수 있다.
  • dispatch는 slice 안에 있는 액션함수들을 호출 할 수 있다.

 

- 필요한 이벤트 핸들러 안에서 액션함수 디스패치하기

  • Slice에서 정의한 액션함수의 action.payload 파라미터로 전달된다.
  • 다수의 파라미터가 필요한 경우 JSON 객체로 묶어서 전달한다.
dispatch(액션함수1(파라미터));
dispatch(액션함수2(파라미터));

 

4. 실습

- index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

// 리덕스 구성을 위한 참조
import { Provider } from 'react-redux';
import store from './store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

 

- App.jsx

import React from 'react';

// Counter.jsx 참조
import Counter from './pages/Counter';

function App() {
  return (
    <div>
      <Counter />
    </div>
  );
}

export default App;

 

- CounterSlice.jsx

// createSlice 함수 참조
import { createSlice } from '@reduxjs/toolkit';

// TODO: Slice 정의 (Action함수 + Reducer의 개념)
const counterSlice = createSlice({
  name: 'counter',

  // 이 모듈이 관리하고자 하는 상태값들을 명시
  initialState: {
    // 여기서는 2개의 상태값을 관리한다.
    number: 0,
    color: '#000',
  },

  // 내부 action 및 동기 action
  // 상태값을 갱신하기 위한 함수들을 구현
  // 컴포넌트에서 이 함수들을 호출할 때는 전달되는 파라미터는 action.payload로 전달된다.
  // initialState와 동일한 구조의 JSON을 리턴한다.
  reducers: {
    plus: (state, action) => {
      const numberValue = state.number + action.payload;
      let colorValue = '#000';

      numberValue > 0 ? (colorValue = '#2f77eb') : (colorValue = '#f60');

      return { number: numberValue, color: colorValue };
    },

    minus: (state, action) => {
      const numberValue = state.number + action.payload;
      let colorValue = '#000';

      numberValue > 0 ? (colorValue = '#2f77eb') : (colorValue = '#f60');

      return { number: numberValue, color: colorValue };
    },
  },
});

export const { plus, minus } = counterSlice.actions;

// 리듀서 객체 내보내기
export default counterSlice.reducer;

 

- Counter.jsx

import React, { memo, useEffect } from 'react';

// 상태값을 로드하기 위한 hook과 action 함수를 dispatch 할 hook 참조
import { useSelector, useDispatch } from 'react-redux';
// Slice에 정의된 액셤함수들 참조
import { plus, minus } from '../slices/CounterSlice';

const Counter = () => {
  // 컴포넌트가 마운트 될 때 콘솔의 모든 내용 삭제함(출력 결과가 복잡해 지는 것을 방지)
  useEffect(() => console.clear(), []);

  // hook을 통해 slice가 관리하는 상태값 가져오기
  const { number, color } = useSelector((state) => state.counter); // store에 있는 counter를 가져옴

  // dispatch 함수 생성
  const dispatch = useDispatch();

  return (
    <div>
      <button onClick={(e) => { dispatch(plus(5)); }}>
        +5
      </button>
      <button onClick={(e) => { dispatch(minus(-3)); }}>
        -3
      </button>

      <h2 style={{ color: color }}>
        {number}
      </h2>
    </div>
  );
};

export default memo(Counter);

 

- store.jsx

// configureStore함수 참조
import { configureStore } from '@reduxjs/toolkit';
// CounterSlice 컴포넌트 참조
import counterSlice from './slices/CounterSlice';

const store = configureStore({
  reducer: {
    // 개발자가 직접 작성한 reducer들이 명시되어야 한다.
    counter: counterSlice,
  },
});

export default store;

※ 출력결과

'국비수업 > React' 카테고리의 다른 글

[React/Redux] 비동기 처리  (0) 2022.05.23
[React] Axios-hooks  (0) 2022.05.17
[React] Axios 사용하기  (0) 2022.05.15
[React] Hooks  (0) 2022.04.29
[React] CSS 사용하기  (0) 2022.04.26

+ Recent posts