aotoyae

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

React

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

aotoyae 2024. 1. 24. 23:18

 

 

💡 리덕스를 활용해 보자~

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

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

 

 

먼저 설치 ~ 터미널에 ~

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 React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
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>
);

// 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();

 

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;

 

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