[React] 전역 상태관리 #redux

💡 전역 상태관리 - Redux

📍 useState의 불편함

컴포넌트의 state를 공유할 때 Props를 통해 부모 컴포넌트에서 자식 컴포넌트로 전달하게 된다. 아니 그래야만 한다.

 

1. 반드시 부모-자식 관계가 되어야 공유가 가능하다.

2. prop drilling이 발생하고 불필요한 코드가 생긴다. (props를 받아서 전달만 하는 컴포넌트는 불필요하다.)

3. 자식 컴포넌트에서 부모 컴포넌트로 값을 보낼 수 없다.

 

📍 리덕스를 사용하면

  • state를 공유할 때 부모-자식 관계가 아니어도 된다
  • 중간에 의미 없는 props의 전달 과정이 없어도 된다. 
  • 자식 컴포넌트에서 만든 state를 부모 컴포넌트에서 사용이 가능하다. 

💡 Global state & Local State

Global state는 redux를 의미, Local state는 useState를 의미한다. 

 

📍 Global state (전역 상태)

Global state는 컴포넌트에서 생성되지 않는다. 중앙에서 state들을 생성하고 관리한다. 즉, 특정 컴포넌트에서 State를 관리하지 않고 중앙에서 관리하는 형태이기 때문에 어떤 컴포넌트에서든지 State를 다이렉트로 접근할 수 있다. 

 

📍  Redux vs Context API

(1) 성능 최적화

`Context API` 는 Provider하위의 모든 컴포넌트를 리렌더링시킬 수 있다. 상태가 변경될 때 모든 컴포넌트가 불필요하게 업데이트하는 것을 막기 위해 최적화 과정이 추가적으로 필요하다(memo, useCallback 등). 반면, `Redux ` 는 상태 변경 시 관련 컴포넌트만 선택적으로 업데이트가 가능하다. 

 

(2) 중앙화와 일관성

`Redux`는 상태를 하나의 저장소에 저장한다. 이로 인해 상태 관련 로직이 중앙에서 관리되어 일관성있게 된다. 따라서 디버깅과 테스팅이 용이하다. 

 

정리

중앙 state 관리소를 사용할 수 있게 도와주는 패키지(라이브러리)이다. 쓰면 편하고 좋다. 


💡 Redux 세팅

 

📍  Redux 설치

yarn add redux react-redux # redux, react-redux 동시 설치

 

📍  Redux 폴더 구조

 ─src
    ├─assets
    ├─components
    ├─context
    └─redux
        ├─config/configStore.js
        └─modules

src하위에 `redux/config`, `redux/modules` 폴더를 생성한다. 그리고 config폴더 하위에 `configStore.js` 파일을 생성한다.

 

config

  • redux설정과 관련된 파일을 놓을 폴더

modules

  • 우리가 만들 state들의 그룹을 묶어놓을 폴더

configStore.js

  • store와 관련된 설정할 파일

 

📍  Redux 기본 설정(?)

configStore.js

import { combineReducers, createStore } from "redux";

// 1) rootReducer를 만든다.
const rootReducer = combineReducers({});

// 2) store 조합
const store = createStore(rootReducer);

// 3) 만든 store export
export default store;

3가지의 작업이 필요하다.

  • rootReducer를 생성하고, 기본값을 설정한다.
  • createStore에는 rootReducer를 전달한다.
  • 반환 받은 store를 export 해준다. 

 

main.jsx

import { Provider } from 'react-redux'
import store from './redux/config/configStore.js'

createRoot(document.getElementById('root')).render(
    <Provider store={store}>
      <App />
    </Provider>
)
  • `react-redux`의 Provide를 import 한다. 
  • 이전에 생성한 store를 import한다. 
  • `<Provider>`로  `<App>` 을 감싸주고 store를 넣어준다. 

여기까지 진행했으면 App 하위의 모든 컴포넌트는 store를 주입한 Provider의 영향권 안에 존재하게 되고 사용할 수 있다. 

 

 

📍 modules

우리 실질적으로 만들 모듈 코드를 작성하면 된다. 아래 counter 모듈을 생성하는 예시를 보자. 

 

modules/counter.js

const initialState = {
    number: 0,
}

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

export default counter;
  • `initialState`: 초기 값을 지정해놓은 변수
  • 리듀서(`counter`): 2개의 매개변수를 전달 받는다. (state, action)
  • 리듀서에 대한 부연설명을 해보자면 리듀서는 함수, 변화를 일으키는 함수이다. 변화의 종류는 action 객체에 따라 처리하도록 구현하면 된다.
  • action에 따른 동작을 switch문으로 작성한다. (if문도 상관은 없지만 switch를 많이 사용한다고 한다.)

다시 돌아가서 우리가 생성한 리듀서를 rootReducer에 전달하면 된다. 

 

configStore.js

import { combineReducers, createStore } from "redux";
import counter from "../modules/counter";

const rootReducer = combineReducers({
    counter,
});
  • 리듀서가 존재하지 않을때는 빈 객체를 주입했지만, 리듀서를 작성하고 나서 `combineReducer`에 주입시켜주면 된다. (새로운 리듀서를 생성할 때 마다 주입하면 된다.)

 

일단 redux를 사용하기 전 기본 세팅은 끝났다. 앞으로는 이런 구조(혹은 편한대로 알아서)로 redux를 사용하면 된다.


💡 Redux 사용하기

rootReducer에 저장된 리듀서들을 사용할 것이다. `useSelector` 라는 훅을 이용하여 꺼내오면 된다. 

 

App.jsx

import { useSelector } from 'react-redux'

function App() {
//  const counterReducer = useSelector((state) => state.counter);
  const counterReducer = useSelector((state) => state);

  console.log('counterReducer', counterReducer.counter);

  return (
    <>
    </>
  )
}
  • `useSelector`: 콜백함수를 매개변수로 받는다. 해당 콜백함수는 state(rootReducer)를 받게 된다.

특정 리듀서 반환

위에서 작성했던 counter 리듀서를 받고 싶다면 두 가지 방법이 있다.

  1. `state.counter`를 사용하여 처음부터 counter 리듀서를 반환 받을 수 있다. (이 방법을 지향하자)
  2. 사용할 때 마다 `counterReducer.counter` 로 사용할 수 있다. 

 

댓글

Designed by JB FACTORY