sohyeon kim

[React] Redux(1) : useSelector, useDispatch, 중앙 데이터 관리 본문

React

[React] Redux(1) : useSelector, useDispatch, 중앙 데이터 관리

aotoyae 2024. 1. 24. 23:18
728x90

 

 

💡 리덕스를 활용해 보자~

props 를 넘겨주는 방식은 계속 지나쳐가는(props 를 넘겨주기만 하는) 컴포넌트가 생길 수 있다.

중앙 데이터 관리소 store 를 만들어서 데이터를 한 번에 가져올 수 있도록 연결해 주자.

 

👀 그런데 Context API 말고 Redux 를 쓰는 이유는?

1. 성능 최적화

Context API 는 Provider 하위 모든 컴포넌트를 리렌더링하게 할 수 있다.

그래서 불필요한 업데이트를 막기 위한 복잡한 최적화 과정이 필요하지만

Redux 는 상태 변경 시 관련 컴포넌트만 선택적으로 업데이트할 수 있어 성능 관리에 용이하다.

 

2. 상태 로직의 중앙화와 일관성

Redux 는 앱의 상태를 하나의 저장소 store 에 저장하기 때문에 상태 고직이 중앙에서 관리되어

더 일관성 있고 예층 가능한 상태 변경이 가능해진다.

또한, 모튼 상태 변경 로직이 리듀서 Reducers 에 의해 처리되기 때문에 디버깅과 테스팅에 용이하다.

 

3. 강력한 미들웨어와 개발 도구

Redux 는 다양한 미들웨어를 지원하여 비동기 작업, 로깅, 상태 변경에 대한 추가 처리 등

복잡한 기능을 구현할 수 있다. 또한, Redux DevTools 와 같은 강력한 개발 도구를 통해

상태 변화를 시각적으로 모니터링하고 이전 상태로 롤백하는 등의 기능을 제공한다.

 

 

npm add redux react-redux

 

 

- redex 폴더 : 리덕스 관련 모든 코드들이 들어가는 곳

- config 폴더 : 리덕스 설정 관련 파일이 들어가는 곳

- configStore.js : 중앙 state(data) 관리소

- modules 폴더 : state 의 그룹! Todolist 라면 Todo.js 등

 

configStore.js

import { createStore } from "redux"; // 새 스토어를 만들어 줄 API
import { combineReducers } from "redux"; // reducer 들을 합쳐줄 API
import counter from "../modules/counter";
import users from "../modules/users";

const rootReducer = combineReducers({ counter, users }); // 합친 reducer 들을 rootReducer 로 저장
const store = createStore(rootReducer); // 그 rootReducer 로 이루어진 store

export default store;

 

새 스토어를 만들었고, 이제 연결하자. (리듀서는 만드는 건 아래에!)

 

index.jsx

import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { Provider } from "react-redux"; {/*import*/}
import store from "./redux/config/configStore"; {/*import*/}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Provider store={store}> {/*지배권 행사하는 Provider 로 하위 컴포넌트를 감싼다.*/}
    <App /> {/*하위 컴포넌트에서 store 에 접근이 가능해진다.*/}
  </Provider>
);

 

modules 폴더에 그룹을 만들어보자.

 

counter.js, users.js

const initialState = { // 초기 상태값 설정(state)
  number: 0,
};

// 리듀서(counter) : 'state 에 변화를 일으키는'함수
// 'state 를 action 의 type 에 따라 변경하는' 함수

// input 으로 state, action 을 받는다.
// state 에 설정해 둔 초기값을 넣는다.
// action 은 type 과 value 를 가지는데 이걸로 state 를 어떻게 바꿀건지를 정한다.
const counter = (state = initialState, action) => {
  switch (action.type) {
    default:
      return state; // 우선 초기값 리턴하도록 함
  }
};

export default counter;
const initialState = {
  userId: 123,
};

const users = (state = initialState, action) => {
  switch (action.type) {
    default:
      return state;
  }
};

export default users;

 

App 에서 reducer(counter, users) 들의 데이터를 읽어오자.

 

App.jsx

import "./App.css";
import { useSelector } from "react-redux"; {/*store에 접근해, reducer 의 값을 읽어온다.*/}

function App() {
  const data = useSelector((state) => { {/*여기서 state 는 중앙 관리소의 전체 데이터들*/}
    return state; {/*그대로 리턴해서*/}
  });

  console.log(data); {/*출력해보자.*/}

  return (
    <>
      <div>Redux~~~~~~~~~</div>
    </>
  );
}

export default App;

// 카운터의 데이터만 가져오고 싶다면
  const data = useSelector((state) => {
    return state.counter;
  });

 

➡️ {number: 0} 출력

 

 

💡 데이터를 변경해 보자!

App.js

import "./App.css";
import { useSelector, useDispatch } from "react-redux";

function App() {
  const counter = useSelector((state) => {
    return state.counter;
  });

  const dispatch = useDispatch(); {/*store 에 바뀐 값을 전달해 줌*/}

  return (
    <>
      <div>현재 카운트 : {counter.number}</div>
      <button
        onClick={() => {
          dispatch({ type: "PLUS_ONE" }); {/*action 에 보낼 값*/}
        }}
      >
        +
      </button>
    </>
  );
}

export default App;

 

counter.js

const initialState = {
  number: 0,
};

const counter = (state = initialState, action) => {
  switch (action.type) {
    case "PLUS_ONE": // 액션의 타입이 PLUS_ONE 으로 전달되면
      return { number: state.number + 1 }; // number 가 + 1 된 새 객체를 반환한다.
    default:
      return state;
  }
};

export default counter;

 

❗️ 그런데 위와 같이 타입을 문자열로 전달해 주면
전달해 준 값이 많아지고, 변경할 시 하나하나 수정하기 힘들어진다.

 

그러니 상수로 지정해 넘겨주자.

 

counter.js

export const PLUS_ONE = "PLUS_ONE"; // 상수 만들고, App 에서도 써야 하니 export!

const initialState = {
  number: 0,
};

const counter = (state = initialState, action) => {
  switch (action.type) {
    case PLUS_ONE:
      return { number: state.number + 1 };
    default:
      return state;
  }
};

export default counter;

 

App.jsx

import { PLUS_ONE } from "./redux/modules/counter";
...
  return (
    <>
      <div>현재 카운트 : {counter.number}</div>
      <button
        onClick={() => {
          dispatch({ type: PLUS_ONE }); {/*가져온 상수로 타입 보냄*/}
        }}
      >
        +
      </button>
  );

 

그럼 똑같이 동작한다!

 

❗️ 여기서 한번 더.. 리팩토링
dispatch 가 액션 객체를 넘겨주고 있는데, 그 객체에 전달해 줄 값이 많아질 수 있다.
type 말고도 더.. 그걸 함수(action creater)로 관리해 보자.

 

counter.js

// action value
const PLUS_ONE = "PLUS_ONE"; // export 안해도 됨
const MINUS_ONE = "MINUS_ONE"; // 지금은 type 밖에 없지만, 아래처럼 함수로 여러 값을 보낼 수 있게 된다.

// action creater : action value를 리턴
export const plusOne = () => { // App 에서 쓰기 위해 export
  return {
    type: PLUS_ONE,
  };
};

export const minusOne = () => { // 마이너스도 추가
  return {
    type: MINUS_ONE,
  };
};


const initialState = {
  number: 0,
};

const counter = (state = initialState, action) => {
  switch (action.type) {
    case PLUS_ONE:
      return { number: state.number + 1 };
    case MINUS_ONE:
      return { number: state.number - 1 };
    default:
      return state;
  }
};

export default counter;

 

App.js

import "./App.css";
import { useSelector, useDispatch } from "react-redux";
import { plusOne, minusOne } from "./redux/modules/counter"; {/*함수 import*/}

function App() {
  const counter = useSelector((state) => {
    return state.counter;
  });

  const dispatch = useDispatch();

  return (
    <>
      <div>현재 카운트 : {counter.number}</div>
      <button
        onClick={() => {
          dispatch(plusOne()); {/*함수의 리턴 값 보냄*/}
        }}
      >
        +
      </button>
      <button
        onClick={() => {
          dispatch(minusOne());
        }}
      >
        -
      </button>
    </>
  );
}

export default App;

 

하드코딩 없이 값을 넘겨주는 중.. 🥲

 

 

 

728x90
반응형