Notice
Recent Posts
Recent Comments
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

MJ.Story

Redux 본문

IT/React

Redux

아토씌 2022. 12. 8. 23:22
  • 리액트에서는 조건이나 동작에 따라 변화할 수 있는 상태를 state라고 부르고, 해당 상태를 하위 컴포넌트에 props의 형태로 전달하여 사용하곤 한다.

  • 다만 위의 그림처럼 상위 컴포넌트에서 하위 컴포넌트로 상태값을 전달하기 위해서는 두 컴포넌트 사이에 있는 모든 컴포넌트를 거쳐 전달해야 하는 단점이 있었는데, 이러다 보니 코드가 복잡해짐과 동시에 상태값을 전달하는 역할의 컴포넌트에서는 사용하지 않을 불필요한 속성값을 받게 된다는 단점이 있었다.
// 하위 컴포넌트 D로 state 값을 전달하는 순서

// without redux
컴포넌트 A state
컴포넌트 B 하위 전달
컴포넌트 C 하위 전달
컴포넌트 D pros

// with redux
[중앙 Store에서 state 관리]
컴포넌트 A
컴포넌트 B
컴포넌트 C
컴포넌트 D store.getState()
  • 그래서 등장한 것이 Flux 아키텍처와 이를 구현한 리덕스(Redux)로, 리덕스를 사용하면 상태값을 중앙 스토어에서 관리할 수 있어 복잡한 컴포넌트 계층 구조에서도 상태를 효과적으로 관리할 수 있다.

리덕스란,

  • 우리가 위에서 말한 “중앙 state 관리소”를 사용할 수 있게 도와주는 패키지(라이브러리) 입니다. “중앙 state 관리소" 를 통해서 State를 관리한다는 아이디어는 굉장히 좋으나, 우리가 그것을 직접 구현하기는 아직 어려우니까요. 패키지(라이브러리)의 도움을 받아 그것을 구현해보고자 합니다.
  • 프론트엔드 개발자들은 “리덕스”를 전역 상태관리 라이브러리 라고 많이 표현합니다. 전역 상태, 즉 Global State를 의미하고 그것을 관리하게 도와주는 라이브러리 (패키지) 이기 때문입니다.

리덕스의 구성

  • 리덕스는 크게 다음 요소들로 구성된다.
  1. 상태값을 저장하는 저장소: 스토어
    • 스토어는 모든 상태값을 저장하며 상태값을 조작할 리듀서 함수를 인자로 받는다.
  2. 상태를 조작하는 함수 : 리듀서 함수
    • 리듀서(Reducer) 함수는 초기 상태값과 액션(action)을 인자로 받아 액션에 따라 조작할 상태를 지정한다.
  3. 액션을 전달하는 함수 : 디스패처 함수
    • 디스패처(Dispatcher) 함수는 액션값과 상태에 관한 데이터를 리듀서 함수에 전달한다.

Global state와 Local state

  • 우리는 앞으로 State를 Global state와 Local state라는 것을 따로 구분지어서 표현할 것 입니다.
    • Local state (지역상태)란?
      • 컴포넌트에서 useState를 이용해서 생성한 state 입니다. 좁은 범위 안에서 생성된 State라고 생각하시면 됩니다.
    • Global state (전역상태)란?
      • Global state는 컴포넌트에서 생성되지 않습니다. 중앙화 된 특별한 곳에서 State들이 생성됩니다.
      • 좀 더 쉽게 얘기해서 “중앙 state관리소” 라고 생각하면 됩니다.

  • 중앙 State관리소에서 State를 생성하고, 만약 어떤 컴포넌트에서 State가 필요하다면 컴포넌트가 어디에 위치하고 있든 상관없이 State를 불러와서 사용 할 수 있게 됩니다. 이렇게 특정 컴포넌트에 종속되어 있는 것이 아니라 “중앙 state관리소” 에서 생성된 State를 Global state라고 합니다. 그리고 이러한 값들을 관리하는 것을 전역 상태 관리 라고 합니다.

정리

  • 리덕스는 전역상태 관리 라이브러리다.
  • 리덕스는 useState를 통해 상태를 관리했을 때 발생하는 불편함을 일부 해소시켜준다.
  • 리덕스는 중앙 State 관리소를 가지고 있으며, 모든 State는 이곳에서 생성된다.
  • useState로 생성한 State는 Local State이고, 리덕스에서 생성한 State는 Global State이다.

알아보면 좋은 키워드

  • propsDrilling란?

리덕스 설정

  1. 리덕스 설치
yarn add redux react-redux
아래와 같은 의미
yarn add redux
yarn add react-redux
  1. 폴더와 파일의 역할
    • redux : 리덕스와 관련된 코드를 모두 모아 놓을 폴더입니다.
    • config : 리덕스 설정과 관련된 파일들을 놓을 폴더 입니다.
    • configStore : “중앙 state관리소”인 Strore를 만드는 설정 코드들이 있는 파일 입니다.
    • modules : 우리가 만들 State들의 그룹이라고 생각하면 됩니다 예를 들어 투두리스트를 만든다고 한다면 투두리스트에 필요한 state들이 모두 모여있을 todos.js를 생성하게 되는데요, 이 todos.js 파일이 곧 하나의 모듈이 됩니다.
  2. src/configStore.js
import { createStore } from "redux";
import { combineReducers } from "redux";

/*
1. createStore()
리덕스의 가장 핵심이 되는 스토어를 만드는 메소드(함수) 입니다. 
리덕스는 단일 스토어로 모든 상태 트리를 관리한다고 설명해 드렸죠? 
리덕스를 사용할 시 creatorStore를 호출할 일은 한 번밖에 없을 거예요.
*/

/*
2. combineReducers()
리덕스는 action —> dispatch —> reducer 순으로 동작한다고 말씀드렸죠? 
이때 애플리케이션이 복잡해지게 되면 reducer 부분을 여러 개로 나눠야 하는 경우가 발생합니다. 
combineReducers은 여러 개의 독립적인 reducer의 반환 값을 하나의 상태 객체로 만들어줍니다.
*/

const rootReducer = combineReducers({}); 
const store = createStore(rootReducer); 

export default store; 
  1. index.js
// 원래부터 있던 코드
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import reportWebVitals from "./reportWebVitals";

// 우리가 추가할 코드
import store from "./redux/config/configStore";
import { Provider } from "react-redux";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(

	//App을 Provider로 감싸주고, configStore에서 export default 한 store를 넣어줍니다.
   
    
  
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: <https://bit.ly/CRA-vitals>
reportWebVitals();

정리

  • 리액트에서 리덕스를 사용하려면, redux, react-redux가 필요하다.
  • 설정코드는 지금 장장 이해 할 필요가 없다.
  • “중앙 State 관리소”를 Store (스토어)라고 부른다.
  • 모듈이란, State들이 그룹이다.

모듈이란?

모듈이란, State의 그룹이라고 했습니다. 우리의 첫 모듈은 카운터 프로그램에 필요한 State들이 모여있는 모듈이 될 것 입니다.

1 .모듈 만들기

  • modules폴더에 counter.js 파일을 생성한다.
// src/modules/counter.js

// 초기 상태값
const initialState = {
  number: 0,
};

// 리듀서
const counter = (state = initialState, action) => {
  switch (action.type) {
    default:
      return state;
  }
};

// 모듈파일에서는 리듀서를 export default 한다.
export default counter;
  1. 모듈의 구성요소 살펴보기
    • initialState === 초기 상태값
    // 초기 상태값
    const initialState = {
      number: 0,
    };
    
    이것이 initialState 입니다. 단어 그대로 초기 상태값 입니다. 즉 어떤 State의 초기값을 정해주는 것 입니다.
    const [number, setNumber] = useState(0) // <- 여기
    
    위 코드에서 만든 State의 초기값은 { } (객체)이고, 그 안에 number 라는 변수에 초기값 0을 할당해준 것입니다. 초기값은 꼭 객체가 아니어도 됩니다. 배열이 되어도 되고, 그냥 원시데이터가 돼도 됩니다. 그리고 객체에서도 여러개의 변수를 넣어줄 수 있습니다.
  2. // 초기값이 0 const initialState = 0; // 초기값이 0이 있는 배열 const initialState = [0]; // 초기값이 number = 0, name = '석구'인 객체 const initialState = { number: 0, name: '석구' };
  3. 우리가 useState를 사용했을 때 괄호 안에 초기값을 지정해주던 것과 같은 이치입니다.

2. Reducer === 변화를 일으키는 함수

  • 아래 코드를 리듀서라고 합니다.
  • 리듀서란, 변화를 일으키는 함수 입니다.
  • 리듀서는 함수다. 이것만 기억해봅시다.
// 리듀서 
const counter = (state = initialState, action) => {
  switch (action.type) {
    default:
      return state;
  }
};
  • 우리가 useState()를 사용할 때, number라는 값을 바꾸고 싶으면 setNumber를 사용했습니다.
  • 아래 코드 처럼 number값을 변경할 수 있었습니다.
// 리듀서 
const counter = (state = initialState, action) => {
  switch (action.type) {
    default:
      return state;
  }
};
  • 리덕스에서는 리듀서가 이 역할을 합니다.
  • 우리가 “리듀서야 number에 +1를 해줘” 라고 명령하며느 리듀서는 number에 +1을 더해줍니다.
  • 그래서 변화를 일으키는 함수라고 표현한 것 입니다.
// src/redux/modules/counter.js

// counter 리듀서
const counter = (state = initialState, action) => {
  switch (action.type) {
    default:
      return state;
  }
};

export default counter; // 여기
  • 그리고 리듀서의 인자에 보면 (state = initialState, action)이라고 되어 있습니다.
  • 우리는 리듀서 인자 첫번째 자리에서는 state를, 두번째 자리에서는 action이라는 것을 꺼내서 사용할 수 있습니다. 이것이 무엇인지는 지금 당장 몰라도 됩니다. 다만 state = initialState처럼 state에 initialState를 할당해줘야 하는 것만 기억하자.
  1. 카운터 모듈을 스토오에 연결하기
  • 우리는 지금까지 모듈파일에서 초기 상태값과 리듀서를 작성했습니다. 이제 우리가 만든 모듈을 스토어에 연결 시켜야 합니다. 아직까진 모듈과 스토어가 각각 따로 분리되어 있는 상태이기 때문에 우리가 만든 State를 스토어에서 꺼낼 수 없습니다.
  • consfigStore.js로 이동해서 아래 코드를 추가해주세요
// src/redux/modules/config/configStore.js

// 원래 있던 코드
import { createStore } from "redux";
import { combineReducers } from "redux";

// 새롭게 추가한 부분
import counter from "../modules/counter";

const rootReducer = combineReducers({
  counter: counter, // <-- 새롭게 추가한 부분
});
const store = createStore(rootReducer);

export default store;
  • 위와 같이 코드를 추가하면, 스토어와 모듈이 연결됩니다. 이렇게 스토어와 모듈을 연결시키는 코드는 우리가 모듈을 추가할 때마다 똑같이 진행해주면 됩니다.

스토어와 모듈 연결 확인하기

  1. useSelector = 스토어 조회
    • 우리가 생성한 모듈을 스토어에 잘 연결했는지 확인하는 방법은 컴포넌트에서 스토어를 직접 조회하면 됩니다. 컴포넌트에서 리덕스 스토어를 조회하고자 할때는 useSelector라는 ‘react-redux’의 훅을 사용해야 합니다.
    • useSelector의 사용법
// 1. store에서 꺼낸 값을 할당 할 변수를 선언합니다.
const number = 

// 2. useSelector()를 변수에 할당해줍니다.
const number = useSelector() 

// 3. useSelector의 인자에 화살표 함수를 넣어줍니다.
const number = useSelector( ()=>{} )

// 4. 화살표 함수의 인자에서 값을 꺼내 return 합니다. 
// 우리가 useSelector를 처음 사용해보는 것이니, state가 어떤 것인지 콘솔로 확인해볼까요?
const number = useSelector((state) => {
	console.log(state)
	return state
});

App.js컴포넌트로 이동해서 기존에 있던 코드를 지우고 아래코드 입력

// src/App.js

import React from "react";
import { useSelector } from "react-redux"; // import 해주세요.

const App = () => {
  const counterStore = useSelector((state) => state); // 추가해주세요.
  console.log(counterStore); // 스토어를 조회해볼까요?

  return <div></div>;
}

export default App;
  • 우리는 컴포넌트에서 스토어를 조회할 때 react-redux에서 제공하는 useSelector라는 훅을 사용합니다(너무중요)!!!
  • 브라우저를 켜고, 콘솔을 보면 아래 이미지처럼 객체가 보이고, 그 안에 counter 라는 값이 있는 것을 볼 수 있습니다. 우리가 만든 counter라는 모듈의 state가 보이는 것을 알 수 있습니다. 이렇게 화살표 함수에서 꺼낸 state라는 인자는 현재 프로젝트에 존재하는 모든 리덕스 모듈의 state 인 것 입니다.
  • 이제 우리는 어떤 컴포넌트에서도 접근 할 수 있는 스토어를 가지게 되었습니다. 만약 우리가 컴포넌트에서 number라는 값을 사용하고자 한다면 아래 코드처럼 꺼내서 사용하면 됩니다.
const number = useSelector(state => state.counter.number); // 0
  1. 정리
  • 보통 모듈은 기능의 이름을 따서 파일을 생성한다.
  • 모듈의 구성요소로는 initialState, Reducer가 있다.
  • 모듈을 만들면, 스토어에 연결을 해야만 한다. 그리고 연결은 configStore.js에서 하낟.
  • 컴포넌트에서 Stroe를 조회할 때는 useSelector를 사용해야 한다.
  • useSelector((state) ⇒ state) 에서 state는 모든 모듈의 state를 조회할 수 있는 값이다.

리덕스의 흐름 도식화

  1. View 에서 액션이 일어난다.
  2. dispatch 에서 action이 일어나게 된다.
  3. action에 의한 reducer함수가 실행되기 전에 middleware가 작동한다.
  4. middleware에서 명령내린 일을 수행하고 난뒤, reducer함수를 실행한다. (3, 4번은 아직 몰라도 된다)
  5. reducer의 실행결과 store에 새로운 값을 저장한다.
  6. store의 state에 subscribe하고 있던 UI에 변경된 값을 준다
  7. counter.js 모듈의 state 수정 기능 만들기 (+1 기능 구현해보기)
  8. // 예시 코드 // local state const [number, setNumber] = useState(0) // click handler const onClickHandler = () => { setNumber(number + 1) }
  • 리덕스에서 값의 수정은 리듀서에서 일아난다.
    1. 리듀서에게 보낼 number를 +1 하라는 “명령”을 만든다.
    2. 명령을 보낸다
    3. 리듀서에서 명령을 받아 number + 1을 한다.
  1. 리듀서에게 보낼 “명령” 만들기
    • 우선 우리는 리듀서에게 number에 +1을 하라고 명령을 보내야겠죠. 명령을 보내기 전에 ‘명령'을 만들어야 합니다. 리덕스에서는 그 명령을 Action 이라고 합니다. 즉, 리듀서에게 내가 어떤 Action을 하길 원한다. 라고 표현하는 것이죠. 행동을 코드로 나타내면 객체 로 만듭니다. 그래서 이것을 액션 객체 라고 합니다.
    • 액션 객체는 반드시 type이라는 key를 가져야 합니다. 왜냐하면 우리가 이 액션 객체를 리듀서에게 보냈을 때 리듀서는 객체 안에서 type이라는 key를 보기 때문입니다.
    // 예시 코드
    //number에 +1 을 하는 액션 객체
    
    { type : "PLUS_ONE" };
    
    • 앞으로 우리는 리덕스 모듈에 있는 state을 변경하기 위해서는 그에 해당하는 액션 객체를 모두 만들어줘야 합니다.
  2. “명령”(액션 객체) 보내기
    • 이제 명령을 만들었으니, 우리는 리듀서에게 명령을 보내야 합니다. 조금 더 정확하게 표현하게 위해서 지금부터는 “명령"이 아닌 액션객체라고 표현하겠습니다.
    • 액션객체를 보내기 리듀서로 보내기위해서는 새로운 훅을 사용해야 합니다. 그 훅은 useDispatch라는 훅 입니다. react-redux에서 import 해서 사용할 수 있으며, 우리가 만든 액션 객체를 리듀서로 보내주는 역할을 하는 훅입니다.
    • useDispatch라는 훅을 사용하기 위해서는 컴포넌트 안에서 아래와 같이 먼저 코드를 작성해서 dispatch라는 변수를 생성해줘야 합니다. 이렇게 생성한 dispatch는 함수 라는 점을 기억하세요! 그래서 우리는 dispatch를 사용할 때 () 를 붙여서 함수를 실행하게 됩니다.
    // src/App.js
    
    import React from "react";
    import { useDispatch } from "react-redux"; // import 해주세요.
    
    const App = () => {
      const dispatch = useDispatch(); // dispatch 생성
      return (
        <div>
          <button
    				// 이벤트 핸들러 추가
            onClick={() => {
    					// 마우스를 클릭했을 때 dispatch가 실행되고, ()안에 있는 액션객체가 리듀서로 전달된다.
              dispatch({ type: "PLUS_ONE" }); 
            }}
          >
    				+ 1
          </button>
        </div>
      );
    };
    
    export default App;
    
    • 자, 이렇게 우리는 디스패치를 이용해서 액션객체를 리듀서로 보낼 수 있습니다. 만약에 어떤 컴포넌트가 렌더링 됐을 때 액션객체를 리듀서로 보내고 싶다면 어떻게 해야 할까요? 한번 고민해보시길 바랍니다.
  3. 액션 객체 받기
    • 우리가 (3)에서 리듀서로 액션을 보내긴 보냈지만, 무언가 눈으로 보이는게 없으니 이게 지금 잘 된건가? 궁금하지 않으신가요? 우리가 액션객체를 리듀서로 보냈으니, 리듀서에서 액션객체가 잘 왔는지 확인해보겠습니다.
    • 아마 현재 counter.js 모듈의 코드가 이런 상태 일 것 입니다. 코드를 수정해볼 것인데요. 우리가 App.js에서 보낸 액션객체를 받을 수 있도록 구현해볼 것 입니다.
      • 리액트에서는 조건이나 동작에 따라 변화할 수 있는 상태를 state라고 부르고, 해당 상태를 하위 컴포넌트에 props의 형태로 전달하여 사용하곤 한다.

      • 다만 위의 그림처럼 상위 컴포넌트에서 하위 컴포넌트로 상태값을 전달하기 위해서는 두 컴포넌트 사이에 있는 모든 컴포넌트를 거쳐 전달해야 하는 단점이 있었는데, 이러다 보니 코드가 복잡해짐과 동시에 상태값을 전달하는 역할의 컴포넌트에서는 사용하지 않을 불필요한 속성값을 받게 된다는 단점이 있었다.
      // 하위 컴포넌트 D로 state 값을 전달하는 순서
      
      // without redux
      컴포넌트 A state
      컴포넌트 B 하위 전달
      컴포넌트 C 하위 전달
      컴포넌트 D pros
      
      // with redux
      [중앙 Store에서 state 관리]
      컴포넌트 A
      컴포넌트 B
      컴포넌트 C
      컴포넌트 D store.getState()
      
      • 그래서 등장한 것이 Flux 아키텍처와 이를 구현한 리덕스(Redux)로, 리덕스를 사용하면 상태값을 중앙 스토어에서 관리할 수 있어 복잡한 컴포넌트 계층 구조에서도 상태를 효과적으로 관리할 수 있다.

'IT > React' 카테고리의 다른 글

Axios란?  (0) 2022.12.18
팀과제  (0) 2022.12.11
React Hooks(useState, useEffect)  (0) 2022.12.08
컴포넌트 렌더링  (0) 2022.12.04
Props 란?  (0) 2022.12.04